next-key lock本质是record lock与gap lock的组合,默认用于rr级别下的范围查询或非唯一索引操作,并非由行锁升级而来;唯一索引等值且命中时才退化为纯行锁。

Next-Key Lock 本质不是锁升级,而是默认加锁行为
很多人说“行锁升级成表锁”,其实是误解。InnoDB 在 REPEATABLE READ 隔离级别下,UPDATE、DELETE、SELECT ... FOR UPDATE 默认就用 Next-Key Lock,它本就是“记录锁 + 间隙锁”的组合,不是从行锁“升级”来的。所谓“非预期锁变多”,往往是因为你只关注了 WHERE 条件匹配的几行,却没意识到 InnoDB 同时锁住了这些行之间的空隙。
范围查询或非唯一索引触发 Next-Key Lock 扩散
只要 WHERE 条件涉及范围(>、、<code>BETWEEN、LIKE 'abc%')或走的是非唯一索引,InnoDB 就会按索引顺序扫描,并对每个扫描到的索引项及其左侧间隙加 Next-Key Lock。即使你只更新一行,也可能锁住一大段区间。
- 例如:索引列
c有值5,10,15,20,执行UPDATE t SET x=1 WHERE c > 8,实际会锁住(5,10]、(10,15]、(15,20]甚至(20,+supremum] - 若
c是普通索引(非唯一),哪怕写WHERE c = 10,也会先锁二级索引项,再回表;过程中若遇到未提交事务或扫描路径延伸,可能额外锁住相邻间隙 -
IS NULL、OR连接多个条件、函数包裹字段(如SUBSTRING(c,1,1))都会让优化器放弃索引,退化为全聚簇索引扫描 → 每行都加Next-Key Lock
唯一索引等值查询是例外,但仅限“存在且精确命中”
只有在主键或唯一索引上使用 = 查询,且该值**真实存在**时,Next-Key Lock 才会退化为纯 Record Lock(只锁那一行)。其他所有情况都不可信:
- 查
id = 9,但表中没有id=9的记录 → 加Gap Lock锁住前后间隙,比如(5,10),阻止插入 - 查
name = 'alice',但name是普通索引 → 即使只有一条匹配,仍可能锁住该索引项对应的所有主键回表路径,以及索引页内相邻间隙 - 隐式类型转换:
WHERE user_id = '123'(user_id是INT)→ 索引失效 → 全表扫描 + 全量Next-Key Lock
验证和规避的关键点容易被忽略
别靠 EXPLAIN 猜锁范围 —— 它只告诉你走不走索引,不告诉你锁什么。真正要看的是运行时锁状态:
- 查
performance_schema.data_locks(MySQL 5.7+),重点关注lock_type(RECORD/GAP/NEXT_KEY)、lock_data(具体锁住的值或间隙) - 避免在 RR 级别下依赖范围条件做高并发更新;如业务允许,可临时降级到
READ COMMITTED(此时无 Gap Lock,但需接受幻读) - 最稳妥的做法:确保 WHERE 字段有合适索引、用等值查询、避免函数/类型转换、监控
INFORMATION_SCHEMA.INNODB_TRX中长事务和锁等待
真正难处理的不是锁本身,而是多个事务在不同索引路径上交叉加锁 —— 比如一个走 idx_a 再锁主键,另一个先锁主键再清理 idx_a,死锁就藏在这种看似无关的操作顺序里。











