文章目录
9.重新认识MySQL锁
9.1MySQL锁概述
- 锁是计算机协调多个进程或者线程并发访问某一资源的机制
- 在数据库中除了传统的对计算资源的争抢之外,数据也是供许多用户共享的一种资源,如何保证多线程下的数据一致性是数据库必须解决的一个问题
9.2锁分类
共有四种角度将MySQL锁分类:
- 锁的粒度:
- 全局锁
- 表级锁
- 行级锁
- 页级锁
- 锁的区间:
- 间隙锁
- 临建锁
- 锁的性能:
- 乐观锁
- 悲观锁
- 锁的级别:
- 共享锁(读锁)
- 排他锁(写锁/独占锁)
9.2.1锁的粒度
- 全局锁:锁住数据库中的所有表
- 表级锁:锁住数据库中的整张表
- 行级锁:锁住对应的行数据
9.2.2锁的区间
-
间隙锁
- 定义:间隙锁用于 锁定一个范围(开区间) 而不是单个行。当事务在范围内的一行上持有锁时,其他事务无法在这个范围内插入新的数据,但是可以在范围之外插入新的数据。
- 举例:假设一个表中的索引列有序且不重复,事务A持有了某个范围内的间隙锁,这个范围可能是一段索引范围,比如(10,20),这时其他事务无法在这个范围内插入新的行。
-
临建锁
- 定义::临建锁是一种结合了行锁和间隙锁的锁类型,它在锁定一个范围时同时锁定了该范围内的行以及范围之间的间隙。
- 举例:考虑一个范围查询,比如查询索引列值在(10,20)之间的行,临建锁会锁定这个范围内的行,同时锁定范围之间的间隙,防止其他事务在这个范围内插入新的行或者改变已有行的值。
9.2.3锁的性能
-
乐观锁:
-
定义:乐观锁认为数据在被操作时很少发生冲突,因此在访问数据时不会立即加锁,而是在更新数据时检查数据是否被其他事务修改过,如果没有则更新成功,否则进行回滚或者重试。
-
举例:在MySQL中,可以使用乐观锁的方式是在更新数据时检查数据的版本号或者时间戳是否与当前操作一致
sqlUPDATE table_name SET column1 = value1, version = new_version WHERE id = x AND version = old_version;
-
-
悲观锁:
-
定义:悲观锁认为数据在被操作时会发生冲突,因此在访问数据之前会先加锁,确保其他事务无法修改该数据,直到当前事务完成操作并释放锁。
-
举例:在MySQL中,可以使用SELECT ... FOR UPDATE语句来获取悲观锁,例如
sqlSELECT * FROM table_name WHERE condition FOR UPDATE;
-
9.2.4锁的级别
-
共享锁:
-
定义:共享锁允许多个事务同时读取同一行数据,但阻止其他事务在该行上获取排他锁;
-
举例:在 MySQL 中,可以使用
SELECT ... LOCK IN SHARE MODE
或者SELECT ... FOR SHARE
来获取共享锁,例如sqlSELECT * FROM table_name WHERE condition LOCK IN SHARE MODE;
-
-
排他锁:
-
定义:排他锁(也称为写锁)阻止其他事务对同一行数据进行读取或写入操作,只有获取了排他锁的事务才能对数据进行修改。
-
举例:在 MySQL 中,可以使用
SELECT ... FOR UPDATE
来获取排他锁,例如sqlSELECT * FROM table_name WHERE condition FOR UPDATE;
-
9.3拓展:意向锁
9.3.1意向锁概述
MySQL 中的意向锁(Intention Lock)是一种用于管理表级锁的锁机制,用于在表级别上指示事务将要在表的哪些行上获取锁。
意向锁并不是实际的锁,而是一种指示,通常用于协调事务对表级锁的获取,以提高并发性能和降低死锁的风险。
9.3.2意向锁分类
可分为两种类型:意向共享锁和意向排他锁
-
意向共享锁(Intention Share Lock):
- 特点:表明事务将要在表的某些行上获取共享锁。当一个事务打算获取某行的共享锁时,会在表级别请求意向共享锁。
- 作用:意向共享锁是为了协调多个事务同时获取共享锁而引入的,在获取共享锁之前,需要先检查是否存在排他锁,以防止与其他事务的排他锁冲突。
-
意向排他锁(Intention Exclusive Lock):
- 特点:表明事务将要在表的某些行上获取排他锁。当一个事务打算获取某行的排他锁时,会在表级别请求意向排他锁。
- 作用:意向排他锁是为了协调多个事务同时获取排他锁而引入的,在获取排他锁之前,需要先检查是否存在共享锁或其他事务的排他锁,以防止与其他事务的共享锁或排他锁冲突。
9.3.3意向锁作用
没有意向锁时,事务在获取表级锁/行级锁之前可能需要频繁地检查整个表,以确定是否有其他事务已经持有了排他锁或共享锁。这会增加系统的开销,降低并发性能。
举例:
事务 A 获取了某一行的排他锁,并未提交:
sql
SELECT * FROM users WHERE id = 6 FOR UPDATE;
事务 B 想要获取 users
表的表锁(共享锁)/行级锁:
cobol
LOCK TABLES users READ;
为共享锁与排他锁互斥
,所以事务 B 在视图对 users
表加共享锁的时候,必须保证:
- 当前没有其他事务持有 users 表的排他锁。
- 当前没有其他事务持有 users 表中任意一行的排他锁 。
为了检测是否满足第二个条件,事务 B 必须在确保 users
表不存在任何排他锁的前提下,去检测表中的每一行是否存在排他锁。
很明显这是一个效率很差的做法,但是有了意向锁之后,情况就不一样了:
(1)意向锁的兼容互斥性
特点:多个事务可以对同一张表多个意向排他锁
意向共享锁(IS) | 意向排他锁(IX) | |
---|---|---|
意向共享锁(IS) | 兼容 | 兼容 |
意向排他锁(IX) | 兼容 | 兼容 |
即意向锁之间是互相兼容的 ,虽然意向锁和自家兄弟互相兼容,但是它会与普通的排他 / 共享锁互斥:
意向共享锁(IS) | 意向排他锁(IX) | |
---|---|---|
共享锁(S) | 兼容 | 互斥 |
排他锁(X) | 互斥 | 互斥 |
注意:这里的排他 / 共享锁指的都是表锁!!!
意向锁不会与行级的共享 / 排他锁互斥!!!
(2)例子1(意向锁和表级的共享/排他锁互斥)
回到刚才 users
表的例子:
事务 A 获取了某一行的排他锁,并未提交:
sql
SELECT * FROM users WHERE id = 6 FOR UPDATE;
- 此时
users
表存在两把锁:users
表上的意向排他锁 与 id 为 6 的数据行上的排他锁。
事务 B 想要获取 users
表的表锁(共享锁):
cobol
LOCK TABLES users READ;
- 此时
事务 B
检测事务 A 持有users
表的意向排他锁 ,就可以得知事务 A
必然持有该表中某些数据行的排他锁, - 么
事务 B
对users
表的加锁请求就会被排斥(阻塞),而无需去检测表中的每一行数据是否存在排他锁。
(3)例子2(意向锁和行级的共享锁/排他锁兼容)
- 意向锁并不会影响到多个事务对不同数据行加排他锁时的并发性
事务 A 获取了某一行的排他锁,并未提交:
sql
SELECT * FROM users WHERE id = 6 FOR UPDATE;
- 此时
users
表存在两把锁:users
表上的意向排他锁- id 为 6 的数据行上的排他锁。
事务 B 想要获取 users
表的表锁(共享锁):
cobol
LOCK TABLES users READ;
- 此时
事务 B
检测事务 A 持有users
表的意向排他锁 ,就可以得知事务 A
必然持有该表中某些数据行的排他锁, - 么
事务 B
对users
表的加锁请求就会被排斥(阻塞),而无需去检测表中的每一行数据是否存在排他锁。
最后事务 C
也想获取 users
表中某一行的排他锁:
cobol
SELECT * FROM users WHERE id = 5 FOR UPDATE;
事务 C
申请users
表的意向排他锁。事务 C
检测到事务 A
持有users
表的意向排他锁。- 因为意向锁之间并不互斥,所以
事务 C
获取到了users
表的意向排他锁。 - 因为id 为 5 的数据行上不存在任何排他锁 ,最终
事务 C
成功获取到了该数据行上的排他锁。
(4)小结
意向锁的引入主要是为了提高并发性能。通过引入意向锁,MySQL 可以更有效地管理表级锁,减少了在并发环境下的锁冲突,从而提高了系统的并发处理能力。
在没有意向锁之前,如果一张表里面已经有行锁了,此时我们再添加表锁,为了防止表锁和行锁发生冲突,表锁就需要遍历整个表中的数据检查是否有行锁。为了优化表锁检索行锁的过程,我们引入意向锁。