在RR级别下加锁情况
表格解析:
|
查询类型 |
索引类型 |
记录是否存在/条件范围 |
加锁规则 |
锁类型 |
示例 (假设索引值: 5, 10, 15, 20) |
|---|---|---|---|---|---|
|
等值查询 |
唯一索引 |
记录存在 |
Next-Key Lock 退化为记录锁 (Record Lock),仅锁定该行记录。 |
X 或 S Record Lock |
|
|
记录不存在 |
Next-Key Lock 退化为间隙锁 (Gap Lock),锁定该值所在的区间。 |
Gap Lock |
| ||
|
非唯一索引 |
记录存在 |
1. 加 Next-Key Lock (左区间)。 |
Next-Key Lock + Gap Lock |
| |
|
记录不存在 |
Next-Key Lock 退化为间隙锁 (Gap Lock),锁定该值所在的区间。 |
Gap Lock |
| ||
|
范围查询 |
唯一索引 |
- |
1. 对满足条件的记录加 Next-Key Lock。 |
Next-Key Lock |
|
|
非唯一索引 |
- |
对扫描过程中所有访问到的记录加 Next-Key Lock,不会退化为记录锁或间隙锁。 |
Next-Key Lock |
|
🔍 详细加锁规则与案例解析
⚙️ 一、核心加锁原则
在深入理解普通索引的等值查询和范围查询之前,需要先掌握 InnoDB 行级加锁的两个核心原则,这是所有加锁规则的基础:
- 1.
加锁的基本单位是 Next-Key Lock:Next-Key Lock 是 Record Lock(记录锁) 和 Gap Lock(间隙锁) 的组合,它是一个左开右闭的区间。例如,如果给索引值
10加了 next-key lock,那么锁定的范围是(5, 10]。 - 2.
查找过程中访问到的对象才会加锁:在通过索引查找记录的过程中,只有那些被存储引擎实际扫描到的索引项才会被加锁。如果没有被访问到,就不会加锁。
🔎 二、普通索引等值查询
等值查询(如 WHERE column = value)的加锁行为会根据索引类型和记录是否存在发生显著变化。
- 1.
查询记录存在时
- •
行为:除了会加 Next-Key Lock 外,还会额外加间隙锁(规则是向右遍历到第一个不符合条件的值才能停止),也就是会加两把锁:查找记录的左区间加 Next-Key Lock,右区间加 Gap lock
。 - •
示例:
SELECT * FROM t WHERE order_id = 16 FOR UPDATE;(假设order_id是普通索引,且值16存在)- •
首先对
order_id=16加上 Next-Key Lock,范围是(8, 16]。 - •
由于是非唯一索引,无法确定是否还有重复的
16,因此会继续向右遍历,找到第一个不等于16的值(例如32)。 - •
对这个区间加上间隙锁
(16, 32)。 - •
最终加锁范围:Next-Key Lock
。(8,16]+ Gap Lock(16,32)
- •
- •
- 2.
查询记录不存在时
- •
行为:Next-Key Lock 会退化为间隙锁(这个规则和唯一索引的等值查询是一样的)
。 - •
示例:
SELECT * FROM t WHERE order_id = 18 FOR UPDATE;(假设order_id=18不存在)- •
首先会尝试定位
order_id=18,找到它应该位于的区间,例如(16, 32)。 - •
对此区间加上 Next-Key Lock
(16,32]。 - •
由于记录不存在,Next-Key Lock 退化为间隙锁
。(16,32)
- •
- •
🔎 三、普通索引范围查询
范围查询(如 WHERE column >= value1 AND column <= value2)的加锁行为与等值查询有所不同,不会发生锁退化
。
- •
行为:范围查询需要一直向右遍历到第一个不满足条件的记录,和唯一索引范围查询不同的是,非唯一索引的范围查询并不会退化成 Record Lock 或者 Gap Lock
。 - •
示例:
SELECT * FROM t WHERE order_id >= 16 AND order_id < 18 FOR UPDATE;- •
首先找到
order_id=16,加上 Next-Key Lock(8,16]。 - •
继续向右范围查找,找到第一个不满足
order_id < 18的记录(例如order_id=32)。 - •
对此记录加上 Next-Key Lock
(16,32]。 - •
最终加锁范围:Next-Key Lock
。(8, 16]和(16, 32],即区间(8, 32]
- •
⚠️ 四、重要注意事项
- 1.
覆盖索引的影响:如果查询使用了覆盖索引(即查询的所有字段都包含在索引中,无需回表查询主键),那么仅会在该索引上加锁,不会再到主键索引上加锁
。这可以减少锁冲突。 - 2.
RC vs RR 隔离级别:上述复杂的加锁规则(尤其是间隙锁和 Next-Key Lock)主要发生在 可重复读(RR) 隔离级别下,目的是防止幻读。在读已提交(RC) 隔离级别下,通常只会有记录锁(Record Lock),间隙锁会被禁用,从而减少锁冲突,但可能遇到幻读问题
。 - 3.
锁的释放时机:InnoDB 中的行锁是在事务提交或回滚后才释放的(两阶段锁协议)。因此,长事务会延长锁的持有时间,增加并发冲突的风险
。
💡 五、最佳实践与优化建议
- 1.
尽量使用唯一索引进行更新操作:唯一索引的等值查询在记录存在时会退化为行锁,能显著减少锁冲突
。 - 2.
删除数据时尽量加 LIMIT:这不仅可以控制删除条数,更重要的是可以显著减小加锁的范围。因为引擎找到满足条件的行数达到 LIMIT 后就会停止扫描,不再继续加锁
。 - 3.
控制事务粒度,及时提交:尽量缩短事务的执行时间,减少锁的持有时间
。 - 4.
使用 EXPLAIN 分析:在执行 SQL 前使用
EXPLAIN命令分析执行计划,确认查询是否使用了预期的索引,避免全表扫描导致锁表
更多推荐



所有评论(0)