最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
MySQL死锁如何定位和解决
时间:2026-08-14 10:38:49 编辑:袖梨 来源:一聚教程网
SHOW ENGINE INNODB STATUS可实时获取最近一次死锁快照,重点查看LATEST DETECTED DEADLOCK段中的两个事务SQL、WAITING FOR THIS LOCK和HOLDS THE LOCK(S)信息;需配合innodb_print_all_deadlocks=ON持久化全量死锁日志,并用INFORMATION_SCHEMA.INNODB_LOCK_WAITS查实时锁等待链,应用层须捕获错误码1213并幂等重试。
MySQL死锁无法避免,但能快速定位、精准归因、稳定恢复——关键不是“怎么不发生”,而是“怎么秒级抓到现场并让应用不崩”。
SHOW ENGINE INNODB STATUS 能看到什么?
这是最直接、最可靠的实时死锁快照,不需要日志轮转或配置重启,执行即得最近一次死锁详情。
重点看输出中 LATEST DETECTED DEADLOCK 区块,它包含:
- 两个事务各自的
TRANSACTION ID和完整 SQL(比如UPDATE orders SET status='shipped' WHERE order_id=1001) - 谁在等哪条记录锁:
WAITING FOR THIS LOCK TO BE GRANTED行明确指出等待的索引、页号、主键值 - 谁持有了对方要的锁:
HOLDS THE LOCK(S)对应的RECORD LOCKS描述了锁定范围 - 最终被回滚的是哪个事务(
WE ROLL BACK TRANSACTION (1))
注意:该命令只返回最近一次死锁。如果频繁发生,仅靠它会漏掉中间发生的多次死锁。
如何让每次死锁都留下痕迹?
靠 innodb_print_all_deadlocks = ON 把所有死锁写进错误日志,这是生产环境必须开的开关。
操作分两步:
- 临时开启(立即生效,重启失效):
SET GLOBAL innodb_print_all_deadlocks = 1; - 永久生效:在
my.cnf的[mysqld]段落添加:innodb_print_all_deadlocks = 1,然后重启 MySQL
开启后,每次死锁都会在 log_error 指定路径(如 /var/log/mysql/error.log)追加一段完整上下文,包括时间戳、线程ID、SQL、锁结构——比 SHOW ENGINE INNODB STATUS 更全、更可追溯。
风险提示:高并发下死锁频发时,日志量可能陡增,需配合日志轮转策略,但绝不应因此关闭该选项。
查当前锁等待关系用哪张表?
当死锁正在发生(还没被检测到或刚发生),或者你想确认是否存在隐性阻塞链时,用 information_schema.INNODB_LOCK_WAITS 关联查询最有效。
标准查询模板:
SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, r.trx_query AS waiting_query, b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, b.trx_query AS blocking_queryFROM information_schema.INNODB_LOCK_WAITS wJOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_idJOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;
结果直接告诉你:哪个线程(waiting_thread)卡在哪条 SQL(waiting_query),被哪个线程(blocking_thread)的哪条 SQL(blocking_query)挡住。
这个查询不依赖死锁是否已触发,只要存在锁等待就可查出,是排查“疑似死锁卡顿”的第一手证据。
应用层该怎么应对死锁错误?
MySQL 返回的错误码是 ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction,应用必须捕获并重试,不能当作普通异常抛出。
重试逻辑要点:
- 只重试幂等写操作(如
UPDATE、INSERT ... ON DUPLICATE KEY UPDATE),非幂等操作(如无条件INSERT)重试可能引发重复数据 - 指数退避:第一次重试延迟 100ms,第二次 200ms,第三次 400ms,避免重试风暴
- 设上限:最多重试 3 次,超限则抛出业务异常,交由人工介入
Python 示例中捕获 e.errno == 1213 就是标准做法;Java 用 SQLException.getSQLState().equals("40001") 判断。
真正容易被忽略的是:重试必须在同一个数据库连接内完成,否则新连接可能拿到旧事务未提交的脏状态,导致逻辑错乱。