limit不等于少锁,因innodb在扫描匹配行时即逐行加锁,扫描多少行就可能锁住多少行及间隙;真正可控方式是用主键范围(如between)替代limit,确保索引命中并减少扫描。

因为 LIMIT 只控制最终修改的行数,不控制加锁过程——InnoDB 在扫描匹配行时就已逐行加锁,扫描多少行,就可能锁住多少行及间隙。
为什么 LIMIT 不等于“少锁”
很多人以为 UPDATE ... LIMIT 10 就只锁 10 行,实际不是。InnoDB 的加锁发生在执行扫描阶段,不是结果截断阶段。它必须先找到满足 WHERE 条件的前 N 行,这个“找”的过程本身就会触发锁。
-
WHERE条件没走索引 → 全表扫描 → 所有行 + 所有间隙都可能被加锁(甚至升级为表锁) - 用了
ORDER BY但字段无索引 → 排序需临时表或文件排序,InnoDB 会按排序逻辑一路扫描并加锁,直到凑够LIMIT数量 - 即使
WHERE id > 1000走了索引,若没配合ORDER BY id ASC,优化器仍可能放弃索引,退化为全表扫描
ORDER BY + LIMIT 是锁扩散高发组合
当语句含 ORDER BY x LIMIT n,而 x 字段不是主键、没有唯一索引或存在大量重复值时,InnoDB 无法靠索引顺序直接定位前 N 行,只能边扫边锁、边排边判,直到攒够数量为止。
- 非唯一字段排序(如
status):相同status值之间的顺序不确定,InnoDB 对整个等值区间加Next-Key Lock - 可重复读(RR)隔离级别下:为防止幻读,会对扫描路径上的间隙加锁,哪怕你只想要 1 行
- 显式加锁(
FOR UPDATE或LOCK IN SHARE MODE)会让锁行为更激进,且暴露得更早
真正可控的加锁方式:用主键范围替代 LIMIT
想锁得少,就得让 MySQL 少扫描。唯一稳定的方式是把“取前 N 行”变成“改指定 ID 区间”,绕过扫描不确定性。
- 用
WHERE id BETWEEN ? AND ?替代LIMIT,确保EXPLAIN显示type: range且key命中主键 - 每批起始值必须来自上一批的
MAX(id),不能简单累加(否则漏数据或重复) - 步长建议 100–500 行:太大易触发锁升级;太小则事务开销压垮 TPS
- 每批后必须
COMMIT,锁立即释放;别指望LIMIT自动帮你缩短锁持有时间
验证到底锁了什么,别猜
表面看是一条 UPDATE ... LIMIT 1,背后可能锁了几万行。唯一可靠的方式是查真实加锁记录:
- 执行完疑似阻塞的语句后,立刻运行:
SELECT ENGINE_LOCK_ID, LOCK_TRX_ID, LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE LOCK_TRX_ID = '你的事务ID' - 重点看
LOCK_DATA:如果是(5, 10)表示间隙锁,10表示行锁,(5, 10]是Next-Key Lock - 如果看到大量
LOCK_MODE为X, GAP或TABLE类型,说明已失控——不是 LIMIT 不管用,是索引和写法没兜住
最麻烦的不是锁本身,而是它藏在 WHERE 和 ORDER BY 后面,不跑 performance_schema.data_locks 根本看不见。你以为改一行,InnoDB 可能已经把整段主键区间、连带空隙全锁死了。











