线上死锁需建立“监控—定位—归因—预防”闭环,立即执行SHOW ENGINE INNODB STATUS\G查看LATEST DETECTED DEADLOCK段落,结合INNODB_TRX与INNODB_LOCK_WAITS分析锁等待,并开启innodb_print_all_deadlocks持久化日志以追溯根因。

线上死锁不是“修一次就完事”,而是必须建立“监控—定位—归因—预防”闭环,否则它会反复在凌晨三点触发报警。
怎么看当前有没有死锁正在发生
别等应用报错才动手。死锁发生后,InnoDB 会自动回滚一个事务并记录日志,但现场信息稍纵即逝。最直接有效的手段是立刻执行:
SHOW ENGINE INNODB STATUS\G
这条命令输出里最关键的段落是 LATEST DETECTED DEADLOCK,它包含两个事务的完整加锁路径、SQL语句、等待的锁类型、甚至被选为牺牲者的理由。注意:它只保留最后一次死锁,所以高频死锁场景下必须配合日志持久化。
- 如果没看到该段落,说明最近没触发死锁检测,但不等于没有锁等待——此时要查
information_schema.INNODB_TRX和INNODB_LOCK_WAITS -
INNODB_LOCK_WAITS能直接关联出谁在等谁、等哪条 SQL,比手动拼接更可靠 - MySQL 8.0+ 可用
performance_schema.data_lock_waits替代旧视图,字段更清晰、性能开销更低
怎么让每次死锁都留下完整证据
靠人工刷 SHOW ENGINE INNODB STATUS 不现实。必须开启全量死锁日志,把每次死锁写进错误日志:
SET GLOBAL innodb_print_all_deadlocks = ON;
这句是临时生效,要永久生效得写进 my.cnf:
[mysqld] innodb_print_all_deadlocks = 1 log_error = /var/log/mysql/error.log
开启后,每条死锁都会以固定格式记入 error log,包含时间戳、事务ID、SQL文本、索引名、锁模式(X 或 S)、锁类型(RECORD / GAP / INSERT_INTENTION)等关键字段。
- 别忽略
log_error路径权限问题:MySQL 进程用户必须有写入权限,否则日志静默失效 - 日志体积增长快,建议配合 logrotate 定期轮转,避免磁盘打满
- 某些云数据库(如阿里云 RDS)不支持动态修改该变量,需在控制台开启“死锁日志”开关
死锁日志里哪些字段真正决定根因
拿到一条典型死锁日志,重点盯住三块:
① *** (1) TRANSACTION: 和 *** (2) TRANSACTION: 下的 mysql tables in use 和 locked tables ——确认是否跨表操作;
② 每个事务的 WEIGHT 值 ——InnoDB 选牺牲者时优先挑权重小的(通常事务持有锁少、修改行数少),但如果你发现总是同一个业务模块被回滚,说明它的事务设计本身偏重;
③ 最关键的是 lock_mode X locks rec but not gap 或 lock_mode X locks gap before rec ——前者是记录锁(安全),后者是间隙锁(高危),大量 gap 锁往往意味着缺失索引或用了范围条件(如 WHERE status IN ('pending', 'processing'))。
- 看到
insert intention锁冲突?大概率是并发 INSERT 同一索引范围,且没走唯一键,触发了隐式间隙锁竞争 - 看到
waiting for this lock to be granted后面跟着index PRIMARY of table `db`.`t_order`,但 SQL 是UPDATE ... WHERE user_id = ??说明user_id没索引,被迫扫描主键,锁住整段范围 - 事务堆栈里出现存储过程或触发器?它们会隐式延长事务生命周期,放大死锁概率
从代码和SQL层面切断死锁链
90% 的线上死锁能通过四条硬规则堵住:
- 所有涉及多表更新/SELECT FOR UPDATE 的事务,严格按**同一顺序**访问表,比如始终先
t_user再t_order,禁止 A 事务先 user 后 order、B 事务先 order 后 user - 批量操作前对 ID 列显式排序:
ORDER BY id,避免因数据物理分布差异导致加锁顺序不一致 - DELETE/UPDATE 必须走索引,执行前用
EXPLAIN确认type是const、ref或range,绝不能是ALL(全表扫描) - 减少事务粒度:把“查-改-发消息-写日志”这种大事务拆成多个小事务,中间状态用幂等表或状态机落地,别让锁横跨 RPC 调用或 MQ 发送
真正难处理的是那些由外键约束、唯一索引冲突、INSERT … ON DUPLICATE KEY UPDATE 引发的隐式锁。这类死锁不会出现在你写的 SQL 里,得靠 SHOW CREATE TABLE 看清约束定义,再针对性加索引或改用 REPLACE INTO。


















