mysql升级不直接引发死锁,但默认行为变化(如隔离级别、索引统计、锁策略)可能暴露原有事务逻辑中的死锁隐患;问题本质是旧代码与新版本语义不兼容。

MySQL 升级本身不会直接引发死锁,但升级后默认行为变化(如隔离级别、索引统计、锁策略)可能暴露原有事务逻辑中的死锁隐患——问题不在升级动作,而在旧代码与新版本语义的不兼容。
怎么看 error log 里真正的死锁记录
升级后首次出现死锁,别急着改代码,先确认是不是真死锁、谁被回滚、锁了什么。InnoDB 只在 SHOW ENGINE INNODB STATUS 中保留**最近一次**死锁详情,日志里则可能被淹没:
- 错误日志中搜索关键词:
Deadlock found when trying to get lock,定位时间戳 - 立刻执行:
SHOW ENGINE INNODB STATUS\G,重点看LATEST DETECTED DEADLOCK区块 - 注意其中两段 SQL 的
lock_mode X locks rec but not gap或lock_mode X locks gap before rec—— 这直接说明是行锁冲突还是间隙锁惹的祸 - 对比升级前后
innodb_lock_wait_timeout值(默认 50 秒),升级若重置为更短值(如 10),可能让原本“卡住”的事务更快报死锁而非等待超时
RR 隔离级别下间隙锁突然变多怎么办
MySQL 5.7 → 8.0 升级后,REPEATABLE READ 下间隙锁行为更严格,尤其对非唯一索引或范围查询。原来没索引的 WHERE status = 'pending',升级后可能因统计信息更新,触发全表扫描+大量间隙锁,和另一事务形成交叉等待。
- 检查死锁日志中涉及的 SQL 是否走了索引:用
EXPLAIN FORMAT=TRADITIONAL对比升级前后执行计划 - 如果
type是ALL或index,且key为NULL,说明缺失索引 → 立即补上覆盖查询条件的索引 - 对只查单值的场景,把普通索引改为
UNIQUE(哪怕业务上不强制唯一),可消除间隙锁(InnoDB 对唯一索引的等值查询只加记录锁) - 临时缓解:在事务内显式关闭间隙锁:
SELECT ... FROM t WHERE ... LOCK IN SHARE MODE改为SELECT ... FROM t WHERE ... FOR UPDATE并确保 where 条件走唯一索引
事务拆分后仍死锁?检查锁顺序一致性
升级后 ORM(如 MyBatis 3.4→3.5)可能改变 SQL 拼接顺序,导致原本按 ID 排序批量更新的语句,变成无序 ID 列表,使不同事务锁行顺序不一致。
- 抓包或开启
general_log,确认两个并发事务实际执行的 SQL 中WHERE id IN (…)的 ID 顺序是否一致 - 禁止 ORM 自动拼 IN 列表;应用层先
ORDER BY id再生成参数,确保所有事务按相同物理顺序加锁 - 对高频并发更新的热点行(如账户余额),用
SELECT ... FOR UPDATE ORDER BY id ASC强制统一顺序,哪怕只更新一行也生效 - 避免在事务中混合使用
UPDATE和INSERT ... ON DUPLICATE KEY UPDATE,后者在 RR 下可能隐式加间隙锁,与前者冲突
为什么重试机制在升级后失效了
MySQL 8.0.22+ 默认启用 innodb_deadlock_detect(无法关闭),但部分云厂商 RDS 在升级后重置了 innodb_rollback_on_timeout 为 OFF,导致锁等待超时后不自动 rollback,而是卡住直到客户端断连——此时看起来像“假死锁”。
- 查当前值:
SELECT @@innodb_rollback_on_timeout;,应为ON - 若为
OFF,需在配置中显式设为ON并重启,否则应用层重试捕获不到Deadlock found错误码(1213),而收到的是连接中断或超时异常 - 应用重试逻辑必须区分错误码:
1205(死锁)可立即重试;1213(锁等待超时)应退避后重试,避免雪崩
真正难处理的不是死锁本身,而是升级后事务行为“变温和”了——比如原来全表锁直接阻塞,现在变成细粒度锁却频繁死锁。这时候盯着 SQL 执行计划和锁类型比调参数更管用。











