MySQL死锁与事务回滚的基本逻辑
MySQL的InnoDB引擎默认开启死锁检测机制,当两个或多个事务互相持有对方需要的锁,且都处于等待状态时,就会触发死锁。此时InnoDB会选择回滚其中一个事务,让另一个事务继续完成操作,被回滚的事务会收到相应的错误提示。
事务回滚后,该事务执行的所有数据修改操作都会被撤销,业务层需要根据错误码判断是否要重试执行该事务。常见的死锁错误码是1213,错误信息会提示事务被回滚是因为检测到死锁。
使用ShowEngineInnoDBStatus查看死锁日志
当发生死锁后,我们可以通过执行SHOW ENGINE INNODB STATUSG命令获取InnoDB引擎的运行状态,其中就包含最近一次死锁的详细日志信息。这个命令的输出内容较多,我们需要重点关注死锁相关的段落。
执行命令获取状态信息
在MySQL客户端中执行以下命令:
-- 查看InnoDB引擎状态,包含死锁日志 SHOW ENGINE INNODB STATUSG
执行后返回的结果中,找到LATEST DETECTED DEADLOCK段落,这段内容就是最近一次死锁的完整记录。
死锁日志关键内容解析
死锁日志主要包含以下几个部分的信息:
- 死锁发生的时间,记录为202X-XX-XX XX:XX:XX格式
- 参与死锁的事务列表,每个事务会标注事务ID、事务状态、持有的锁和等待的锁
- 每个事务正在执行的SQL语句,方便定位具体业务操作
- 最终被回滚的事务标识,说明哪个事务被牺牲掉以解除死锁
日志示例解析
以下是一个简化后的死锁日志片段,我们逐一解析内容:
------------------------ LATEST DETECTED DEADLOCK ------------------------ 2024-05-20 14:30:22 0x7f8b2c1a3700 *** (1) TRANSACTION: TRANSACTION 123456, ACTIVE 2 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s) MySQL thread id 10, OS thread handle 140248345583360, query id 123 localhost root updating UPDATE user SET balance = balance - 100 WHERE id = 1 *** (1) WAITING FOR: RECORD LOCKS space id 58 page no 3 n bits 72 index PRIMARY of table `test`.`user` trx id 123456 lock_mode X waiting *** (2) TRANSACTION: TRANSACTION 123457, ACTIVE 1 sec starting index read mysql tables in use 1, locked 1 2 lock struct(s), heap size 1136, 1 row lock(s) MySQL thread id 11, OS thread handle 140248345583361, query id 124 localhost root updating UPDATE user SET balance = balance - 50 WHERE id = 2 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 58 page no 3 n bits 72 index PRIMARY of table `test`.`user` trx id 123457 lock_mode X *** (2) WAITING FOR: RECORD LOCKS space id 58 page no 3 n bits 72 index PRIMARY of table `test`.`user` trx id 123457 lock_mode X waiting *** WE ROLL BACK TRANSACTION (1)
从上面的日志可以看出,事务123456持有id=2的行的排他锁,等待id=1的行的排他锁;事务123457持有id=1的行的排他锁,等待id=2的行的排他锁,形成死锁。最终InnoDB回滚了事务123456,事务123457可以继续执行。
根据死锁日志优化业务
拿到死锁日志后,我们可以从以下几个方向优化业务,减少死锁发生:
- 统一事务中操作数据的顺序,比如所有事务都按照id从小到大的顺序更新数据,避免出现循环等待锁的情况
- 尽量缩小事务的范围,减少事务持有锁的时间,避免长事务占用锁资源
- 给查询和更新语句添加合适的索引,避免行锁升级为表锁,减少锁冲突的概率
- 对于非核心的事务操作,可以设置合理的锁等待超时时间,避免无限等待
常见问题说明
需要注意的是,SHOW ENGINE INNODB STATUS只会记录最近一次的死锁信息,如果死锁发生后又有新的死锁,之前的日志会被覆盖。如果需要留存所有死锁日志,可以开启InnoDB的死锁日志输出到错误日志的功能,在配置文件中添加innodb_print_all_deadlocks=ON即可。
另外,事务回滚后,业务层捕获到1213错误时,可以根据业务场景选择重试执行该事务,重试时需要注意控制重试次数,避免频繁重试加重数据库负担。
MySQL死锁事务回滚ShowEngineInnoDBStatusInnoDB死锁日志修改时间:2026-07-02 14:09:35