mysql的limit语句锁行远超size,因加锁发生在扫描阶段而非结果截断阶段:需先扫描offset+size行才能定位,期间所有访问的索引项、记录及间隙均被next-key lock锁定。

因为 MySQL 的 LIMIT 本身不控制加锁范围,它只在扫描完成后再“截断结果”,而 InnoDB 的行锁(Next-key Lock)是在扫描过程中就施加的 —— 扫多少行,就可能锁多少行。
为什么 LIMIT offset, size 会锁住远超 size 行?
关键在于:加锁发生在数据扫描阶段,不是结果返回阶段。MySQL 必须先定位到第 offset + 1 行,才能开始取 size 行;这个“定位过程”中所有访问过的索引项、记录和间隙,都会被加锁。
- 若
ORDER BY字段无索引或用的是非唯一索引(如status),InnoDB 会扫描所有匹配WHERE条件的索引项,并对整个扫描路径加 Next-key Lock - 哪怕只
LIMIT 10,当OFFSET是 200000 时,前 200010 行都在扫描路径上,全都被锁住 - 主键
ORDER BY id相对好些,但大OFFSET仍会锁住大量主键区间,尤其在并发更新频繁时容易阻塞
LIMIT 配合 FOR UPDATE 或 LOCK IN SHARE MODE 时更危险
显式加锁语句会让问题暴露得更直接:锁不是“选完再加”,而是“边扫边加”。一旦扫描深度失控,锁范围就爆炸。
-
SELECT * FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 10 OFFSET 200000 FOR UPDATE:只要created_at没索引,或status是非唯一索引,InnoDB 就可能锁住几十万条记录及其间隙 - 即使加了
WHERE id > ?,如果没配合正确的ORDER BY方向(比如ORDER BY id ASC却用id > ?),优化器可能放弃索引,退化为全表扫描 -
SELECT ... FOR UPDATE LIMIT 1看似安全,但如果WHERE条件没走索引,照样会锁整张表(表锁)
如何验证当前查询到底锁了哪些范围?
别猜,直接查 performance_schema.data_locks。这是唯一能看清实际加锁对象的方式。
- 执行疑似阻塞的查询后,立即运行:
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或X, REC_NOT_GAP,说明锁已扩散;若出现TABLE类型锁,基本确认走了全表扫描
真正麻烦的不是锁本身,而是锁的“不可见性”——你没写 FOR UPDATE,但只要用了 WHERE + ORDER BY + LIMIT 且索引设计不当,InnoDB 就可能自动上 Next-key Lock。游标分页能绕过这个问题,但前提是排序字段必须是唯一、递增、客户端可透传的,否则漏数据比慢还难排查。











