MySQL 8.0在幻读处理上更严格:唯一索引等值查询仅主键const查可省间隙锁,其余均强制Next-Key Lock;5.7则存在隐式退化,易致幻读。

MySQL 5.7 和 8.0 在幻读处理上的差异,核心不是“要不要加 Next-Key Lock”,而是“什么时候加、加多宽、是否允许退化”——尤其在 SELECT ... FOR UPDATE 或 UPDATE 场景下,8.0 的间隙锁(gap lock)行为更严格、更可预测,但也更容易暴露冲突。
Next-Key Lock 在 5.7 中的“宽松退化”现象
5.7 对间隙锁的判断存在隐式宽松逻辑:当唯一索引查找命中某行时,若该行存在且满足 WHERE 条件,InnoDB 有时会跳过对右侧间隙的加锁(即不加 Next-Key Lock,只加记录锁)。这看似减少锁范围,实则埋下幻读隐患。
- 典型触发场景:
UPDATE t SET x=1 WHERE id = 100,而id是唯一索引,且该行确实存在 → 5.7 可能只锁住记录本身,不锁(100, next_id)这个间隙 - 后果:并发事务仍可
INSERT INTO t VALUES (105, ...)(只要不违反唯一约束),导致后续相同条件的SELECT ... FOR UPDATE看到新插入行,构成幻读 - 这种退化不可控,依赖内部路径判断,应用层无法预期或规避
MySQL 8.0 强制 Next-Key Lock 覆盖所有扫描路径
8.0 明确要求:只要走索引进行范围扫描(即使是等值查找 + 唯一索引),只要涉及间隙判断(比如确认“100 后面没别的 100”),就必须对对应间隙加锁。这不是 bug 修复,而是语义收紧。
- 同上例:
UPDATE t SET x=1 WHERE id = 100→ 8.0 一定加 Next-Key Lock,覆盖(100, next_id),阻止任何在该间隙内的INSERT - 效果:RR 隔离级别下幻读概率大幅下降,行为更符合 SQL 标准对“可重复读”的定义
- 代价:高并发写入热点主键时,
INSERT更容易被阻塞,innodb_row_lock_waits上升明显(尤其在自增主键+高频 INSERT ON DUPLICATE KEY UPDATE 场景)
唯一索引 + 等值查询时,8.0 的“不加间隙锁”仅限一种情况
8.0 并非全盘收紧——它把“可安全省略间隙锁”的条件收得极窄,只保留一个明确可验证的情形:
- 必须是**聚簇索引(主键)上的等值查找**,且记录**精确命中**(不是范围扫描)
- WHERE 条件中不能含函数、表达式、隐式类型转换(如
WHERE id = '100'若id是 INT,会触发隐式转换,导致走全索引扫描而非单记录查找) - 执行计划必须显示
type: const或type: eq_ref,且key列明确指向主键 - 一旦出现
type: ref或扫描多行(哪怕只有一行满足),就按 Next-Key Lock 处理
最容易被忽略的实战陷阱:ORDER BY + LIMIT 不再帮你“躲开”间隙锁
很多老代码靠 SELECT ... FOR UPDATE ORDER BY id DESC LIMIT 1 拿最新一行并加锁,以为只锁一行。但在 8.0 下,如果 id 是自增主键,这个查询实际会扫描索引末尾,并对“最大 id 之后的间隙”加锁——也就是锁住整个“未来插入空间”。
- 5.7 中可能只锁住查到的那一行;8.0 中该语句等价于锁住
(max_id, +∞) - 后果:后续所有
INSERT都会被阻塞,直到该事务结束 - 解法不是去掉
LIMIT,而是改用SELECT ... FOR UPDATE WHERE id = (SELECT MAX(id) FROM t)(确保走主键等值查找)


















