InnoDB 死锁会回滚整个受害事务
当多个事务形成不兼容锁请求的等待环时,就发生 InnoDB 死锁。开启死锁检测后,引擎选择一个受害者,回滚它的整个事务,使其他参与者能够继续。应用收到错误 1213(SQLSTATE 40001)。若只重试报错的那条语句,就会漏掉同一事务中更早执行、但已经一起被回滚的工作。 坏掉的样子 不能给所有锁错误套用同一种回滚规则。默认关闭超时整笔回滚时,锁等待超时只撤销正在等待的那条语句,事务内此前执行的语句仍然待决。开启对应服务端选项后,超时才会改为回滚整个事务。死锁本来就会回滚整个受害事务,不能把默认超时的语句级行为误套在死锁上。 受害者选择也不保证某个固定会话永远输。应用必须检查真实错误,再以有界次数重试完整逻辑事务。不可逆的外部副作用如果没有单独去重契约,不应跟着数据库事务自动重放。提交回复途中连接断开,也不等于已经得到死锁回滚的确定结论;错误类型与已知事务结果需要分开判断。 快速验证 在可丢弃的 MySQL 8.4 实例上打开甲、乙和观察者三个全新管理客户端,先确认三个会话均处于自动提交模式,关闭客户端自动重新连接,保持会话身份稳定。本次实际执行版本是 MySQL 8.4.11。观察者先创建尚未使用的实验模式,并核对死锁检测开启、超时采用默认语句级回滚: CREATE DATABASE xbcatm_deadlock_lab; USE xbcatm_deadlock_lab; CREATE TABLE accounts (id INT PRIMARY KEY, balance INT NOT NULL) ENGINE=InnoDB; INSERT INTO accounts VALUES (1,100),(2,100); SELECT @@innodb_deadlock_detect,@@innodb_rollback_on_timeout; 两个设置应分别为一和零。甲开始一项给两个账户各增加十的操作,先锁住第一个账户: USE xbcatm_deadlock_lab; SET SESSION innodb_lock_wait_timeout=30; SET @increment=10; START TRANSACTION; UPDATE accounts SET balance=balance+@increment WHERE id=1; 乙开始一项各增加一百的操作,先锁住第二个账户: ...