LIMIT更新易死锁,因未走索引导致全表扫描,不同事务加锁顺序不一致形成环路;根本解法是WHERE与ORDER BY共用满足最左前缀的联合索引,并用主键范围分批替代ORDER BY + LIMIT。

为什么LIMIT更新反而更容易死锁?
很多人以为加了LIMIT就能控制锁范围,结果在高并发下频繁触发Deadlock found when trying to get lock。根本原因不是LIMIT本身有问题,而是MySQL执行时可能没走索引,导致全表扫描——不同事务扫描起始位置不同,加锁顺序不一致,天然形成死锁环路。比如UPDATE t SET status=1 WHERE type='pending' ORDER BY id LIMIT 100,如果type没索引,InnoDB会逐行扫描并加锁,事务A从开头扫、事务B从中间扫,锁住的行物理顺序完全不同。
确保WHERE + ORDER BY字段命中同一联合索引
这是最直接有效的解法。必须让查询条件和排序字段共用一个联合索引,并满足最左前缀原则:
-
WHERE type = ? AND status = ? ORDER BY id→ 建联合索引(type, status, id) -
WHERE user_id = ? ORDER BY created_at DESC→ 建联合索引(user_id, created_at) - 避免在索引列上用函数:
WHERE DATE(created_at) = '2024-01-01'会让索引失效 - 用
EXPLAIN验证:type不能是ALL或index,key字段必须显示实际使用的索引名
用主键分批替代ORDER BY + LIMIT
当业务允许时,比“排序取前N”更安全的做法是“按主键范围切片”。它彻底规避扫描顺序不确定性:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 先查出最小
id:SELECT MIN(id) FROM t WHERE type='pending' - 再分批更新:
UPDATE t SET status=1 WHERE type='pending' AND id BETWEEN ? AND ? - 每次更新后记录上一批最大
id,作为下一批起点,避免漏数据 - 相比
ORDER BY id LIMIT 100,这种写法即使并发执行,所有事务都按id升序加锁,不会形成环路
批量更新必须配事务超时与重试逻辑
LIMIT分批更新不是万能保险,仍需兜底:
- 显式设置事务超时:
SET innodb_lock_wait_timeout = 5,避免单个批次卡死太久 - 应用层捕获
Deadlock found when trying to get lock错误,自动重试当前批次(不是整个任务) - 禁止在事务内做RPC、日志IO等耗时操作,否则锁持有时间不可控
- 慎用
REPEAT ... UNTIL存储过程循环——它把所有批次塞进一个事务,锁会累积释放,反而扩大风险
真正容易被忽略的是:即便加了索引,如果WHERE条件选择率过高(比如status IN ('a','b','c')匹配80%的行),MySQL仍可能放弃索引走全表扫描。上线前务必用真实数据量+慢日志+EXPLAIN FORMAT=TREE交叉验证执行计划。










