制造一个真实的经典死锁,再让两个事务的'已完成工作量'明显不对等,重复多轮看InnoDB选择受害者的真实倾向。
经典的死锁场景:G先锁10再要20,H先锁20再要10。
session GSTART TRANSACTION; UPDATE accounts SET balance=balance-1 WHERE id=10; SELECT SLEEP(1); UPDATE accounts SET balance=balance-1 WHERE id=20; -- 此时H已经锁住了20,G在这里等 COMMIT; session H(晚0.3秒启动)START TRANSACTION; UPDATE accounts SET balance=balance-1 WHERE id=20; SELECT SLEEP(1); UPDATE accounts SET balance=balance-1 WHERE id=10; -- 此时G已经锁住了10,H在这里等 → 死锁 COMMIT;
*** (1) TRANSACTION: ... query "UPDATE accounts ... id=20" 持有id=10的锁,等待id=20 *** (2) TRANSACTION: ... query "UPDATE accounts ... id=10" 持有id=20的锁,等待id=10 *** WE ROLL BACK TRANSACTION (2) ← 真实回滚了事务(2),也就是session H session H的真实错误:ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
两个事务真实互相等待对方持有的锁,形成了一个环,InnoDB的死锁检测线程真实发现了这个循环并选择了一方强制回滚——这次测试里是session H被回滚,session G的事务真实继续执行完成并提交。
把G改成只修改1行(10和20)就冲突,H改成先修改5行(20,30,40,50,60)再去抢10,两边"投入的工作量"真实不对等。
轮次1:I(改1行后冲突)真实死锁被回滚,J(改5行后冲突)真实存活 轮次2:I(改1行后冲突)真实死锁被回滚,J(改5行后冲突)真实存活 轮次3:I(改1行后冲突)真实死锁被回滚,J(改5行后冲突)真实存活
每一步都是本文上面用真实并发事务跑出来的原始结果。
SHOW ENGINE INNODB STATUS里的LATEST DETECTED DEADLOCK:查看真实的锁等待环和回滚决定