DELETE慢最常见原因是WHERE条件列无索引,导致全表扫描;需确保索引命中、避免函数/类型转换、合理设计联合索引顺序,并用有序字段分批删除。

WHERE条件列没索引,直接触发全表扫描
DELETE慢最常见原因不是数据量大,而是WHERE里的字段压根没索引。比如DELETE FROM orders WHERE status = 'canceled'执行几秒甚至几分钟,大概率是status列没建索引。InnoDB必须逐行读取、判断、加锁、写日志,I/O和锁开销爆炸式增长。
实操建议:
- 先用
EXPLAIN DELETE FROM orders WHERE status = 'canceled'看type是否为ALL——这是全表扫描的明确信号 - 别只看“有没有索引”,要看“是否命中”。比如已有
(user_id, status)联合索引,但查询只写了WHERE status = 'canceled',照样不走索引(最左前缀失效) - 对低区分度字段(如只有3–5个值的
status),单列索引效果有限;但如果该列常和其他高区分度字段组合过滤(如user_id),就值得建联合索引
函数或类型转换让索引彻底失效
写了WHERE DATE(created_at) = '2024-01-01'或WHERE user_id = '123'(user_id是BIGINT),哪怕created_at或user_id有索引也白搭。数据库无法用B+树索引加速计算后的结果。
实操建议:
- 把
DATE(created_at) = '2024-01-01'改成created_at >= '2024-01-01 00:00:00' AND created_at ,确保能走<code>created_at索引 - 统一类型:数字字段就用数字比较,
WHERE user_id = 123,别传字符串 - 避免
!=、NOT IN、OR(多个分支未共用同一索引时),这些容易让优化器放弃索引
复合索引顺序错了,等于没建
索引列顺序直接影响WHERE能否命中。比如经常执行DELETE FROM logs WHERE app = 'web' AND level = 'error' AND ts > '2026-08-01',但建的是(level, app, ts),那app作为第二列就无法被最左前缀利用,实际还是全表扫描。
实操建议:
- 把过滤性最强(区分度最高)的列放最左。例如
user_id有百万级唯一值,status只有3种,那就优先(user_id, status, created_at) - 范围查询(
>、BETWEEN)后面的列无法用于索引查找。所以(a, b, c)上WHERE a = 1 AND b > 10 AND c = 5,只有a和b能用索引,c得回表过滤 - 已有
(a, b, c),再建(a, b)基本冗余;但(a, c)可能有用——如果查询常是WHERE a = ? AND c > ?
分批删时索引没配合主键/时间戳推进
加了索引,但还是一次性DELETE FROM t WHERE status = 'old' LIMIT 1000,性能仍可能崩:LIMIT在无序字段上行为不可控,MySQL可能每次重新扫描全表找前1000行,越往后越慢。
实操建议:
- 用有序字段推进:比如
DELETE FROM t WHERE status = 'old' AND id > 100000 ORDER BY id LIMIT 1000,下一批从id > 1001000开始 - 避免
OFFSET分页删:LIMIT 1000 OFFSET 100000会跳过前10万行,扫描成本线性增长 - 确保推进字段(如
id)本身有索引,否则id > N也走不了索引

















