mysql如何避免事务中的死锁_mysql死锁预防策略

3次阅读

死锁本质是事务以不同顺序加锁同一组资源导致的循环等待,预防核心是统一访问顺序;update/delete必须走索引且where条件符合最左前缀原则;批量操作须按主键升序处理;事务粒度宜小,避免外部调用阻塞锁。

mysql如何避免事务中的死锁_mysql死锁预防策略

死锁不是配置问题,是访问顺序冲突

mysql 死锁无法通过调大 innodb_lock_wait_timeout 或关掉自动提交来“避免”,它本质是多个事务以不同顺序加锁同一组资源(比如两行记录、两个索引项)导致的循环等待。预防的核心是让所有事务按**相同顺序**访问资源。

UPDATE/DELETE 必须走索引,且顺序要可控

无索引的 UPDATEDELETE 会触发全表扫描+行锁升级,锁住大量无关记录,极大增加死锁概率。更危险的是:优化器可能因统计信息过期或参数变化,某次走索引、下次走全表,导致访问顺序不一致。

  • 确保 WHERE 条件中所有字段都落在同一个复合索引的最左前缀上(例如索引 (user_id, status, created_at),WHERE 中用 user_id = ? AND status = ? 是安全的;只用 status = ? 就可能失效)
  • EXPLAIN 检查每条 DML 的执行计划,确认 key 列非 NULLrows 值稳定且合理
  • 避免在事务中先 select ... for UPDATEUPDATE,除非你能保证 SELECT 返回的行序与后续 UPDATE 的 WHERE 顺序严格一致(通常很难)

批量操作必须按主键升序处理

当业务需要更新一批记录(如订单状态批量变更),若直接用 IN (100, 5, 88),InnoDB 内部可能按任意顺序加锁——而另一个事务恰好用 IN (5, 88, 100),就构成死锁条件。

  • 应用层先查出 ID 列表,显式排序:ORDER BY id ASC
  • 拆成小批量(如每次 100 行),用 WHERE id IN (?,?,?) + ORDER BY id(虽然 IN 本身不保序,但配合 ORDER BY 和唯一主键,InnoDB 会按主键物理顺序加锁)
  • 极端场景下,改用逐条 UPDATE ... WHERE id = ? 并按 ID 升序执行,牺牲吞吐换确定性

事务粒度越小越好,别在事务里调外部服务

一个持有行锁的事务如果卡在 http 调用、文件读写或 sleep 上,等于把锁占着不动,其他事务只能干等——这不是死锁,但会显著放大死锁发生率(因为等待窗口变长,更多事务挤进同一资源竞争)。

  • 把日志记录、消息发送、缓存更新等非数据库操作移到事务 提交之后
  • 避免在事务中做任何不可控耗时操作;如果必须查外部 API,先查好数据再进事务
  • SELECT ... FOR UPDATE NOWAIT 主动失败,比无限等待更利于快速重试(需捕获 Lock wait timeout exceeded 错误)

真正难防的死锁往往藏在看似无关的二级索引更新里——比如你只更新 status 字段,但该字段上有索引,InnoDB 就要同时维护聚簇索引和二级索引的锁。这时候光看主键顺序不够,得结合实际执行计划和索引结构一起看。

text=ZqhQzanResources