制造一个经典死锁:A锁行1想要行2,B锁行2想要行1,用SHOW ENGINE INNODB STATUS拿到InnoDB自己记录的死锁检测报告,看它选谁做牺牲品。
A先锁行1,B先锁行2,然后A想要行2、B想要行1——一个的死锁。
操作时间线A: START TRANSACTION B: START TRANSACTION A: UPDATE t4 SET v=v+1 WHERE id=1 → A锁住行1 B: UPDATE t4 SET v=v+1 WHERE id=2 → B锁住行2 A: UPDATE t4 SET v=v+1 WHERE id=2 → A想要行2(B拿着) (等待0.3秒后) B: UPDATE t4 SET v=v+1 WHERE id=1 → B想要行1(A拿着)——死锁形成
两个事务互相等对方手里的锁,InnoDB不会一直卡死,会检测出来并处理。
结果A的第二条UPDATE:成功,耗时0.3037秒(约等于B发起冲突请求前的等待时间) B的第二条UPDATE:立刻失败,耗时0.0012秒 → Error 1213: Deadlock found when trying to get lock; try restarting transaction
结果很清楚:B的请求几乎立刻(1.2毫秒)就被报错打回,而A的请求在B被回滚的瞬间就成功了。InnoDB并不会像普通锁等待那样傻等到超时(默认50秒),而是主动检测出了这个循环等待,选择其中一个事务作为"牺牲品"直接报错,把锁释放给另一个事务继续。
SHOW ENGINE INNODB STATUS里有一段专门记录最近一次死锁的细节。
的LATEST DETECTED DEADLOCK片段*** (1) TRANSACTION: TRANSACTION 2307 ... UPDATE t4 SET v=v+1 WHERE id=2 *** (1) HOLDS THE LOCK(S): ... index PRIMARY of table demo.t4 ... (对应id=1这一行) *** (1) WAITING FOR THIS LOCK TO BE GRANTED: ... (对应id=2这一行) *** (2) TRANSACTION: TRANSACTION 2308 ... UPDATE t4 SET v=v+1 WHERE id=1 *** (2) HOLDS THE LOCK(S): ... (对应id=2这一行) *** (2) WAITING FOR THIS LOCK TO BE GRANTED: ... (对应id=1这一行) *** WE ROLL BACK TRANSACTION (2)
这份报告把整个循环等待原原本本记录了下来:事务(1)拿着id=1的锁,等id=2;事务(2)拿着id=2的锁,等id=1——两个事务互相牵制,谁都走不下去。最后一行 WE ROLL BACK TRANSACTION (2) 就是InnoDB做出的裁决:回滚持有较少undo日志(代价更小)的那个事务,让另一个能继续执行。
每一步都是本文上面用两个MySQL连接跑出来的原始结果。
SHOW ENGINE INNODB STATUS:的LATEST DETECTED DEADLOCK取证报告