死锁时rollback回滚到事务起始点(begin或隐式启动),丢弃全部变更;应用须捕获sqlstate='40001'并重试整个业务单元,而非单条sql。

死锁发生时,ROLLBACK 会回滚到哪里?
MySQL 遇到死锁后自动选一个事务做牺牲者,对它执行 ROLLBACK —— 但这个回滚不是“回滚到上一条语句”,而是回滚到该事务最开始的 BEGIN 或隐式启动点。也就是说,整个事务的所有变更都会丢弃,不存在“只撤回最后几条”的情况。
常见错误现象:Deadlock found when trying to get lock; try restarting transaction 报错后,应用没重试或直接忽略,导致业务逻辑断在中间,数据状态不一致。
- 必须在应用层捕获该错误(SQLSTATE = '40001'),并重试整个业务单元(不是单条 SQL)
- 避免在事务里混用不同隔离级别或嵌套事务(MySQL 不支持真正的嵌套事务)
- 如果用了
SAVEPOINT,死锁触发的ROLLBACK仍会清除所有保存点,ROLLBACK TO SAVEPOINT不会生效
怎么写事务才不容易被选为死锁牺牲者?
MySQL 死锁检测器本身不看事务大小或执行时间,但会优先选择 ROLLBACK 成本更低的那个——通常是修改行数更少、持有锁更少、事务已运行时间更短的。所以“小而快”是降低被选中概率的关键。
使用场景:高并发下单、库存扣减、账户余额更新等竞争热点操作。
- 按固定顺序访问多张表(比如总是先
UPDATE orders再UPDATE inventory),避免交叉加锁 - 尽量缩短事务生命周期:不要在事务里调外部 HTTP、发邮件、或做复杂计算
- 避免在事务中使用
SELECT ... FOR UPDATE锁全表或范围过大(如没走索引的WHERE status = ?) - 批量操作拆成小批次(例如每次最多 100 行),减少单次持有的锁数量和时间
innodb_deadlock_detect 关掉能防死锁吗?
不能。关掉只是让 MySQL 停止主动检测死锁,转而依赖 innodb_lock_wait_timeout 超时机制来“被动”释放。结果是:死锁依然存在,只是从秒级报错变成几十秒卡住再报错,用户体验更差,且可能拖垮连接池。
性能影响:开启检测本身开销极小(仅在加锁路径上多一次哈希查环),但关掉会导致大量线程长时间等待,CPU 和连接数双双飙升。
- 生产环境必须保持
innodb_deadlock_detect = ON(默认就是 ON) - 如果真遇到高频死锁,应排查 SQL 访问模式,而不是关检测
- 超时时间
innodb_lock_wait_timeout建议设为 10–30 秒,太短会导致误杀正常长事务,太长会阻塞下游
事务提交前崩溃,数据会不会丢?
只要开启了 innodb_flush_log_at_trx_commit = 1(默认值),并且磁盘没故障,就不会丢。InnoDB 在 COMMIT 返回成功前,已把 redo log 刷到磁盘,崩溃恢复时能重放。
容易踩的坑:有些 DBA 为了“提升性能”改成 =2(只刷 OS cache)或 =0(完全不刷),这会让事务在 mysqld 进程崩溃时丢失,哪怕磁盘完好。
- 金融、订单、账户类系统严禁改这个值
- 如果用的是云数据库(如 RDS、PolarDB),确认其底层是否强制覆盖了该参数
-
sync_binlog = 1也要同步开启,否则主从数据可能不一致
真正难处理的从来不是死锁本身,而是事务边界和业务语义没对齐——比如一个“支付成功”状态更新和“发通知”绑在同一个事务里,前者必须强一致,后者其实可以异步。这种耦合才是数据完整性漏洞的源头。











