mysql 8.0在非唯一索引上锁范围扩大是升级后阻塞与死锁主因,因innodb对next-key lock判定更严格;需通过performance_schema.data_locks查真实锁类型(record/gap/insert_intention),而非仅看transaction_isolation。

SELECT FOR UPDATE 和 UPDATE WHERE 在非唯一索引上锁范围变大,是升级后最常触发阻塞和死锁的根源。MySQL 8.0 并未改变 REPEATABLE READ 隔离级别本身,但 InnoDB 对间隙锁(Next-Key Lock)的判定更严格,5.7 中“侥幸通过”的语句,在 8.0 下大概率卡住。
怎么看真实锁行为,而不是只查隔离级别?
别信 SELECT @@transaction_isolation 返回结果——它永远显示 REPEATABLE-READ,掩盖了底层锁行为变化。关键要查运行时实际加了什么锁:
- 用
performance_schema.data_locks查活跃事务持有的锁类型:RECORD(行锁)、GAP(间隙锁)、INSERT_INTENTION(插入意向锁) - 重点过滤条件:
WHERE THREAD_ID IN (SELECT THREAD_ID FROM performance_schema.threads WHERE PROCESSLIST_COMMAND = 'Sleep'),排除空闲线程干扰 - 配合并发压测:在测试环境开两个事务,同时执行
UPDATE t SET x=1 WHERE status='pending',再查data_locks看锁住的INDEX_NAME和LOCK_DATA范围是否比 5.7 明显扩大
哪些 SQL 模式最容易暴露锁问题?
以下写法在 5.7 可能只锁几行,到 8.0 会锁住整个间隙甚至全表:
-
UPDATE或SELECT FOR UPDATE使用非唯一索引字段(如status、type),且该字段重复值多 -
SELECT FOR UPDATE查不到数据时:5.7 常不加锁或只锁索引项;8.0 默认加GAP锁,后续相同条件的INSERT会被阻塞 -
WHERE条件含隐式类型转换,例如WHERE mobile = 13800138000(mobile是VARCHAR):8.0 优化器更早放弃走索引,触发全表扫描 + 锁升级 - 事务中混用
ALTER TABLE:8.0 的原子 DDL 要求更强的元数据锁(MDL),哪怕只是REPEATABLE READ事务,也可能被长时间阻塞
为什么 innodb_locks_unsafe_for_binlog 移除这么致命?
这个变量在 5.7 中可关闭 Next-Key Lock,让 SELECT FOR UPDATE 退化为仅记录锁(Record Lock),很多业务逻辑依赖它“降级”锁行为。但在 8.0 中它已被彻底移除:
- 所有
SELECT FOR UPDATE和SELECT LOCK IN SHARE MODE都强制走 Next-Key Lock - 过去靠该变量“掩护”的宽松读写逻辑,升级后必须重构:要么加唯一索引约束,要么改用
SELECT ... FOR UPDATE NOWAIT主动报错,避免无限等待 - 未显式指定索引的
UPDATE/DELETE:若走全表扫描,8.0 锁的是全部聚簇索引记录 + 所有间隙;5.7 可能只锁实际命中的行
最容易被忽略的验证点是什么?
锁问题从不报错,也不影响服务启动——它只在高并发真实流量下爆发。你必须在测试环境模拟“查-算-更”完整链路,并观察:
-
SHOW ENGINE INNODB STATUS\G中的TRANSACTIONS和LOCK WAIT段落,确认是否有新增等待链 - 慢查询日志里是否突然出现大量
Waiting for table metadata lock或Waiting for global read lock - 应用监控中事务平均耗时、锁等待时间、死锁次数是否在压测后陡增
别等上线后半夜收告警才想起查锁——那时已经晚了。











