MySQL死锁日志深度解读与实战排查指南 1. 死锁现象与基础排查手段第一次在监控系统里看到数据库连接数飙升时我的手心直冒汗。那是个周五的下午电商平台的订单提交接口突然大面积超时。打开终端连上数据库输入SHOW ENGINE INNODB STATUS后满屏的锁等待信息让我意识到——我们遇到了典型的MySQL死锁。死锁的本质就像两个人在窄桥相遇事务A握着资源X等待资源Y事务B却握着资源Y在等资源X。MySQL检测到这种循环等待会自动触发死锁处理机制但我们需要快速定位问题源头。以下是每个DBA都应该掌握的紧急排查命令-- 查看当前锁等待情况MySQL 5.7 SELECT * FROM performance_schema.events_waits_current WHERE EVENT_NAME LIKE %lock%; -- 经典的三张锁信息表适用于所有版本 SELECT * FROM information_schema.INNODB_TRX; -- 运行中的事务 SELECT * FROM information_schema.INNODB_LOCKS; -- 持有的锁 SELECT * FROM information_schema.INNODB_LOCK_WAITS;-- 锁等待关系上周处理的一个案例特别典型批量订单状态更新时两个事务以相反顺序更新了相同的订单记录。通过INNODB_LOCK_WAITS表发现事务1234在等待事务5678释放行锁而事务5678又在等待事务1234的另一把锁典型的交叉更新死锁。2. 死锁日志深度解析拿到SHOW ENGINE INNODB STATUS输出的死锁日志时别被那一大段十六进制吓倒。我们来看个真实案例的关键部分LATEST DETECTED DEADLOCK ------------------------ 2024-03-15 14:22:17 0x7f445c2b6700 *** (1) TRANSACTION: TRANSACTION 3025831, ACTIVE 12 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s) UPDATE orders SET statuspaid WHERE order_id1005 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 85 page no 4 n bits 72 index PRIMARY of table db.orders trx id 3025831 lock_mode X locks rec but not gap waiting *** (2) TRANSACTION: TRANSACTION 3025830, ACTIVE 15 sec updating or deleting mysql tables in use 1, locked 1 4 lock struct(s), heap size 1136, 3 row lock(s) UPDATE order_items SET shipped1 WHERE order_id1005 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 85 page no 4 n bits 72 index PRIMARY of table db.orders trx id 3025830 lock_mode X locks rec but not gap *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 86 page no 3 n bits 80 index idx_order_id of table db.order_items trx id 3025830 lock_mode X locks rec but not gap waiting日志拆解指南事务时间线事务3025831比3025830晚3秒启动锁模式解读lock_mode X是排他锁rec but not gap是记录锁非间隙锁关键冲突点事务3025830持有orders表主键锁order_id1005事务3025831在等待同一个主键锁事务3025830又在等待order_items表的二级索引锁这个死锁的根本原因是应用层先更新订单主表再更新条目表的模式与另一个服务先更新条目表再更新主表的模式产生了资源竞争。3. 六种典型死锁场景分析3.1 交叉更新死锁-- 事务A BEGIN; UPDATE products SET stockstock-1 WHERE id1; UPDATE products SET stockstock-1 WHERE id2; -- 事务B BEGIN; UPDATE products SET stockstock-1 WHERE id2; UPDATE products SET stockstock-1 WHERE id1;解决方案统一更新顺序比如按照id升序处理。3.2 间隙锁冲突在RR隔离级别下这个场景经常让人措手不及-- 事务A SELECT * FROM orders WHERE amount 100 FOR UPDATE; -- 事务B INSERT INTO orders(amount) VALUES (150); -- 被阻塞优化方案如果业务允许改用RC隔离级别避免间隙锁。3.3 唯一键冲突批量导入数据时最容易触发-- 事务A INSERT INTO users(email) VALUES (atest.com); -- 事务B INSERT INTO users(email) VALUES (atest.com); -- 等待 -- 事务A再插入其他记录就会死锁规避方法使用INSERT IGNORE或ON DUPLICATE KEY UPDATE。4. 系统化排查流程建立标准排查流程能节省大量时间这是我的检查清单紧急止血-- 查找阻塞源头 SELECT blocking_pid, COUNT(*) FROM sys.innodb_lock_waits GROUP BY blocking_pid; -- 终止恶性进程 KILL 12345;日志收集# 持续监控死锁日志 mysqladmin debug 21 | grep -A 50 LATEST DETECTED DEADLOCK模式分析统计高频死锁表grep -o table.* deadlock.log | sort | uniq -c绘制事务时序图用工具可视化锁等待关系复现验证-- 在测试环境模拟 SET GLOBAL innodb_status_output_locksON; -- 执行可疑事务序列5. 预防与优化策略应用层优化所有事务按照固定顺序访问资源缩短事务执行时间避免用户交互批量操作使用LIMIT分片处理数据库配置# my.cnf关键参数 innodb_lock_wait_timeout30 # 默认50秒太长 innodb_deadlock_detectON # 8.0默认开启 transaction_isolationREAD-COMMITTED架构设计热点数据采用队列串行处理使用Redis缓存减轻数据库压力考虑改用乐观锁机制记得去年双十一大促前我们通过分析历史死锁日志发现90%的死锁集中在库存扣减场景。最终通过引入Redis分布式锁本地事务的组合方案将死锁发生率降为零。这提醒我们——治本之策永远是优化业务逻辑而不是单纯调优数据库参数。