加锁的对象是索引,加锁的基本单位是 next-key lock,它是由记录锁和间隙锁组合而成的,next-key lock 是前开后闭区间,而间隙锁是前开后开区间。
MySQL 查询是怎么加锁的?
MySQL 查询是怎么加行级锁的?
- 基本单位是 next-key lock,由记录锁和间隙锁构成
- 会以最小成本进行加锁,next-key lock 在一些场景下会退化成记录锁或间隙锁
- 唯一索引
- 等值查询:
- 记录存在,加记录锁
- 记录不存在,加间隙锁(可重复读,读已提交,没有间隙锁)
- 范围查询(条件中的记录,如<5,那么5就是条件记录)
- 对每一个扫描到的索引加 next-key 锁
- 大于等于
- 如果条件记录存在表中,对该记录加记录锁(因为左开右壁闭)
- 其余都是next-key锁
- 小于或小于等于:
- 如果小于,最后的区间加next-key锁
- 如果小于等于:
- 条件记录不存在表中,该区间加间隙锁,其余加next-key锁
- 否则条件记录也加next-key锁
- 等值查询:
- 非唯一索引:
- 因为还存在主键索引,所以两个索引加锁(主键索引只有满足条件的记录才会加记录锁)
- 等值查询:
- 记录存在
- 会扫描所有的等值记录(非唯一),扫描过程中加next-key锁
- 对第一个不符合条件的二级索引记录加间隙锁,不符合条件的记录可以修改删除、但是因为不符合条件的记录有间隙锁,不能再插入等值的数据(只要保证下次查询结果不变即可)
- 对符合条件的记录的主键索引加记录锁
- 记录不存在
- 只有第一条不符合记录的二级索引加间隙锁,
- 没有主键索引加锁(没有符合的)
- 记录存在
- 没有走索引
- 如果锁定读查询,没有走索引,会全表扫描,全部加上next-key锁,等价于==锁全表==
- update、delete如果查询条件不加索引,也会==锁全表==
非唯一索引范围查询
非唯一索引和主键索引的范围查询的加锁也有所不同,不同之处在于非唯一索引范围查询,索引的 next-key lock 不会有退化为间隙锁和记录锁的情况,也就是非唯一索引进行范围查询时,对二级索引记录加锁都是加 next-key 锁。
就带大家简单分析一下,事务 A 的这条范围查询语句:
sql
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user where age >= 22 for update;
+----+-----------+-----+
| id | name | age |
+----+-----------+-----+
| 10 | 山治 | 22 |
| 20 | 香克斯 | 39 |
+----+-----------+-----+
2 rows in set (0.01 sec)事务 A 的加锁变化:
- 最开始要找的第一行是 age = 22,虽然范围查询语句包含等值查询,但是这里不是唯一索引范围查询,所以是不会发生退化锁的现象,因此对该二级索引记录加 next-key 锁,范围是 (21, 22]。同时,对 age = 22 这条记录的主键索引加记录锁,即对 id = 10 这一行记录的主键索引加记录锁。
- 由于是范围查询,接着继续扫描已经存在的二级索引记录。扫面的第二行是 age = 39 的二级索引记录,于是对该二级索引记录加 next-key 锁,范围是 (22, 39],同时,对 age = 39 这条记录的主键索引加记录锁,即对 id = 20 这一行记录的主键索引加记录锁。
- 虽然我们看见表中最后一条二级索引记录是 age = 39 的记录,但是实际在 Innodb 存储引擎中,会用一个特殊的记录来标识最后一条记录,该特殊的记录的名字叫 supremum pseudo-record,所以扫描第二行的时候,也就扫描到了这个特殊记录的时候,会对该二级索引记录加的是范围为 (39, +∞] 的 next-key 锁。
- 停止查询
可以看到,事务 A 对主键索引和二级索引都加了 X 型的锁:

- 主键索引(id 列):
- 在 id = 10 这条记录的主键索引上,加了记录锁,意味着其他事务无法更新或者删除 id = 10 的这一行记录。
- 在 id = 20 这条记录的主键索引上,加了记录锁,意味着其他事务无法更新或者删除 id = 20 的这一行记录。
- 二级索引(age 列):
- 在 age = 22 这条记录的二级索引上,加了范围为 (21, 22] 的 next-key 锁,意味着其他事务无法更新或者删除 age = 22 的这一些新记录,不过对于是否可以插入 age = 21 和 age = 22 的新记录,还需要看新记录的 id 值,有些情况是可以成功插入的,而一些情况则无法插入,具体哪些情况,我们前面也讲了。
- 在 age = 39 这条记录的二级索引上,加了范围为 (22, 39] 的 next-key 锁,意味着其他事务无法更新或者删除 age = 39 的这一些记录,也无法插入 age 值为 23、24、25、...、38 的这一些新记录。不过对于是否可以插入 age = 22 和 age = 39 的新记录,还需要看新记录的 id 值,有些情况是可以成功插入的,而一些情况则无法插入,具体哪些情况,我们前面也讲了。
- 在特殊的记录(supremum pseudo-record)的二级索引上,加了范围为 (39, +∞] 的 next-key 锁,意味着其他事务无法插入 age 值大于 39 的这些新记录。
在 age >= 22 的范围查询中,明明查询 age = 22 的记录存在并且属于等值查询,为什么不会像唯一索引那样,将 age = 22 记录的二级索引上的 next-key 锁退化为记录锁?
因为 age 字段是非唯一索引,不具有唯一性,所以如果只加记录锁(记录锁无法防止插入,只能防止删除或者修改),就会导致其他事务插入一条 age = 22 的记录,这样前后两次查询的结果集就不相同了,出现了幻读现象。
- 如果是age>22,只会锁age=39的临键锁和∞
- 因为不用担心插入age=22的数据,虽然age =22 且id >10 的也无法插入,右侧就是临键锁
- 如果是 age<19,只会锁 age = 19 的临键锁
- 因为不用担心age =19的插入,虽然age =19且id<1 无法插入,右侧就是临键锁
- 如果是 age <=19 , 会锁 age = 19 和 age = 20的临键锁
- 因为要防止 age = 19 的记录插入, age = 19 的临键锁只能锁 age <=19 且 id <1的部分, age = 19 且 id > 1的部分需要通过 age = 20 的临键锁进行限制
