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

来源:这里教程网 时间:2026-02-28 20:51:19 作者:

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

MySQL 死锁无法通过调大

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

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

无索引的

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

确保 WHERE 条件中所有字段都落在同一个复合索引的最左前缀上(例如索引
(user_id, status, created_at)
,WHERE 中用
user_id = ? AND status = ?
是安全的;只用
status = ?
就可能失效)
EXPLAIN
检查每条 DML 的执行计划,确认
key
列非 NULL,
rows
值稳定且合理
避免在事务中先
SELECT ... FOR UPDATE
UPDATE
,除非你能保证 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 就要同时维护聚簇索引和二级索引的锁。这时候光看主键顺序不够,得结合实际执行计划和索引结构一起看。

相关推荐