delete + limit 并非先查后删,而是边查边删:执行器遍历过程中按where匹配、order by排序维护候选窗口,删够limit行即终止;无索引时即使limit 1也可能全表扫描,且加锁范围由where决定,与limit无关。

DELETE + LIMIT 的执行顺序不是“先查再删”,而是“边查边删”
MySQL 的 DELETE 语句加了 LIMIT 后,**不会先把所有匹配行全扫描出来再挑前 N 条删**。它是在执行器逐行遍历过程中,一边用 WHERE 判断是否匹配、一边按 ORDER BY 排序逻辑维护一个“候选窗口”,一旦删够 LIMIT 行就立刻终止扫描。
这意味着:
- 没走索引时,哪怕你只
LIMIT 1,也可能触发全表扫描(因为得找第一个匹配项) -
ORDER BY字段若无索引,排序开销会发生在内存或临时文件里,不加速查找,反而拖慢整个过程 - 如果 WHERE 条件匹配行数少于
LIMIT值,语句正常结束,不影响事务一致性
ORDER BY 必须存在,否则 LIMIT 删除的“哪几行”不可控
不带 ORDER BY 的 DELETE ... LIMIT N,删除的是存储引擎物理顺序下的前 N 行——对 InnoDB 来说,这基本等价于聚簇索引的自然顺序(通常是主键递增),但你无法依赖它。
例如:
DELETE FROM logs WHERE status = 'pending' LIMIT 10;
这条语句实际删的是满足 status = 'pending' 的、在 B+ 树中**首次被遍历到的 10 行**,顺序取决于索引结构和查询优化器选的访问路径(比如走 status 索引还是全表扫),结果不可预测。
要确保删“最老的 10 条”,必须显式写:
DELETE FROM logs WHERE status = 'pending' ORDER BY created_at ASC LIMIT 10;
且 created_at 最好和 status 组成联合索引,否则 ORDER BY 会触发 filesort。
加锁范围由 WHERE 条件决定,与 LIMIT 无关
LIMIT 只控制最终“标记为删除”的行数,**不减少加锁范围**。InnoDB 在执行阶段一开始就会对 WHERE 条件覆盖的整个索引范围加 next-key lock(记录锁 + 间隙锁),防止幻读。
常见误判:
- 以为
DELETE ... WHERE status = 1 LIMIT 1只锁 1 行 → 实际锁所有status = 1的行及其间隙 - 以为加了
LIMIT就能避免锁表 → 若WHERE没走索引,仍会全表加锁 - 在事务中执行该语句后立刻查不到被删行,但其他事务仍可能被阻塞,直到你提交或回滚
验证方式始终是:EXPLAIN FORMAT=TREE 看是否命中索引,再结合 SELECT ... FOR UPDATE 模拟锁行为测试。
多表 DELETE 不支持 LIMIT,强行加会直接报错
这是最容易忽略的语法硬限制:
DELETE t1, t2 FROM t1 JOIN t2 ON t1.id = t2.t1_id WHERE t1.created_at <p>MySQL 所有版本都会报错:<code>ERROR 1064 (42000): You have an error in your SQL syntax</code>。</p> <p>替代方案只有两种:</p>
- 拆成单表语句,用子查询先取 ID 列表:
DELETE FROM t1 WHERE id IN (SELECT id FROM t1 WHERE ... ORDER BY id LIMIT 100) - 用应用层分批:先
SELECT id FROM t1 WHERE ... ORDER BY id LIMIT 100,再DELETE FROM t1 WHERE id IN (...)
注意子查询方式在 MySQL 8.0.20+ 支持 LIMIT,但旧版本需用派生表绕过限制;另外,IN 列表长度受限于 max_allowed_packet,大批量仍建议应用层控制。











