表格解析:

查询类型

索引类型

记录是否存在/条件范围

加锁规则

锁类型

示例 (假设索引值: 5, 10, 15, 20)

​等值查询​

​唯一索引​

记录存在

Next-Key Lock 退化为​​记录锁 (Record Lock)​​,仅锁定该行记录。

X 或 S Record Lock

WHERE id = 15→ 锁 id=15

记录不存在

Next-Key Lock 退化为​​间隙锁 (Gap Lock)​​,锁定该值所在的区间。

Gap Lock

WHERE id = 12→ 锁 (10, 15)

​非唯一索引​

记录存在

1. 加 Next-Key Lock (左区间)。
2. ​​还会向右遍历​​至第一个不满足条件的值,并为此区间加间隙锁。

Next-Key Lock + Gap Lock

WHERE a = 10→ 锁 (5,10](10,15)

记录不存在

Next-Key Lock 退化为​​间隙锁 (Gap Lock)​​,锁定该值所在的区间。

Gap Lock

WHERE a = 12→ 锁 (10,15)

​范围查询​

​唯一索引​

-

1. 对满足条件的记录加 Next-Key Lock。
2. ​​会继续向右扫描​​到第一个不满足条件的记录并加锁(已知Bug,高版本可能修复)。

Next-Key Lock

WHERE id >=10 AND id <15→ 锁 [10](10,15]+ (可能锁(15,20])

​非唯一索引​

-

对扫描过程中所有访问到的记录加 Next-Key Lock,​​不会退化为记录锁或间隙锁​​。

Next-Key Lock

WHERE a >=10 AND a <15→ 锁 (5,10](10,15]


🔍 详细加锁规则与案例解析

⚙️ 一、核心加锁原则

在深入理解普通索引的等值查询和范围查询之前,需要先掌握 InnoDB 行级加锁的两个核心原则,这是所有加锁规则的基础:

  1. 1.

    ​加锁的基本单位是 Next-Key Lock​​:Next-Key Lock 是 ​​Record Lock(记录锁)​​ 和 ​​Gap Lock(间隙锁)​​ 的组合,它是一个​​左开右闭​​的区间。例如,如果给索引值 10加了 next-key lock,那么锁定的范围是 (5, 10]

  2. 2.

    ​查找过程中访问到的对象才会加锁​​:在通过索引查找记录的过程中,只有那些被存储引擎​​实际扫描到的索引项​​才会被加锁。如果没有被访问到,就不会加锁。

🔎 二、普通索引等值查询

等值查询(如 WHERE column = value)的加锁行为会根据​​索引类型​​和​​记录是否存在​​发生显著变化。

  1. 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. 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. 1.

    ​覆盖索引的影响​​:如果查询使用了​​覆盖索引​​(即查询的所有字段都包含在索引中,无需回表查询主键),那么​​仅会在该索引上加锁​​,不会再到主键索引上加锁

    。这可以减少锁冲突。
  2. 2.

    ​RC vs RR 隔离级别​​:上述复杂的加锁规则(尤其是间隙锁和 Next-Key Lock)主要发生在 ​​可重复读(RR)​​ 隔离级别下,目的是防止幻读。在​​读已提交(RC)​​ 隔离级别下,​​通常只会有记录锁(Record Lock)​​,间隙锁会被禁用,从而减少锁冲突,但可能遇到幻读问题

  3. 3.

    ​锁的释放时机​​:InnoDB 中的行锁是在​​事务提交或回滚后​​才释放的(两阶段锁协议)。因此,长事务会延长锁的持有时间,增加并发冲突的风险

💡 五、最佳实践与优化建议
  1. 1.

    ​尽量使用唯一索引进行更新操作​​:唯一索引的等值查询在记录存在时会退化为行锁,能显著减少锁冲突

  2. 2.

    ​删除数据时尽量加 LIMIT​​:这不仅可以控制删除条数,更重要的是可以​​显著减小加锁的范围​​。因为引擎找到满足条件的行数达到 LIMIT 后就会停止扫描,不再继续加锁

  3. 3.

    ​控制事务粒度,及时提交​​:尽量缩短事务的执行时间,减少锁的持有时间

  4. 4.

    ​使用 EXPLAIN 分析​​:在执行 SQL 前使用 EXPLAIN命令分析执行计划,确认查询是否使用了预期的索引,避免全表扫描导致锁表

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐