MySQL死锁无法完全避免,但可通过开启innodb_print_all_deadlocks、分析SHOW ENGINE INNODB STATUS日志定位原因,并按固定顺序访问表、确保索引命中、缩短事务等优化减少发生;应用层需捕获错误码1213并指数退避重试。

MySQL死锁无法完全避免,但能快速定位、分析并减少发生频率。关键在于理解死锁成因(多个事务循环等待彼此持有的锁),并借助系统工具获取现场信息,再针对性优化。
查看死锁日志定位原因
MySQL默认开启死锁检测,每次发生后会在错误日志中记录详细信息(InnoDB状态快照)。需确认innodb_print_all_deadlocks=ON(5.6.2+默认关闭,建议开启):
- 编辑my.cnf,在[mysqld]下添加:innodb_print_all_deadlocks = ON
- 重启MySQL或动态设置(仅对新连接生效):SET GLOBAL innodb_print_all_deadlocks = ON;
- 查错日志路径:SHOW VARIABLES LIKE 'log_error';,然后用tail -f实时观察
使用SHOW ENGINE INNODB STATUS分析
执行该命令可获取最近一次死锁的完整上下文,重点关注LATEST DETECTED DEADLOCK段:
- 会显示两个(或多个)事务的SQL、持有锁类型(Record Lock / Gap Lock / Next-Key Lock)、等待锁类型、涉及的表和索引
- 注意TRANSACTION块中的mysql tables in use和locked tables,判断是否因未走索引导致全表扫描加锁
- 若看到lock_mode X locks rec but not gap waiting,说明是行锁冲突;若含gap,可能与唯一键冲突或范围查询有关
常见死锁场景与规避方法
多数死锁源于事务内SQL执行顺序不一致或索引失效,而非并发本身:
- 按固定顺序访问多张表:例如事务A先更新user再更新order,事务B反向操作就易死锁。统一约定“先user后order”可消除循环等待
- 确保WHERE条件命中索引:无索引的UPDATE/DELETE会升级为表级意向锁,大幅增加冲突概率。用EXPLAIN验证执行计划
- 避免长事务:事务越长,持锁时间越久,冲突窗口越大。业务逻辑拆分、减少事务内非DB操作(如远程调用、复杂计算)
- 减少锁粒度:用主键精确更新代替模糊条件;批量操作拆分为小批次;必要时用SELECT ... FOR UPDATE SKIP LOCKED跳过已锁定行
应用层应对策略
数据库层优化后仍偶发死锁,应用需具备重试能力:
- 捕获MySQL错误码1213(Deadlock found when trying to get lock)
- 对写操作实现指数退避重试(如100ms、200ms、400ms),最多3次,避免雪崩
- 读操作一般无需重试,但若依赖强一致性且刚写完就读,可考虑加SELECT ... LOCK IN SHARE MODE或调整隔离级别










