EXPLAIN显示type为ALL说明DELETE未走索引,主因是WHERE条件未命中有效索引,如无索引、隐式类型转换、函数操作、最左前缀不匹配、IN子查询失效或IS NULL处理不当,需通过EXPLAIN验证并优化索引设计与SQL写法。

EXPLAIN 显示 type: ALL 就说明没走索引
执行 EXPLAIN DELETE FROM t WHERE status = 'pending',如果结果中 type 是 ALL、key 是 NULL、rows 接近表总行数,那就不是“偶尔慢”,而是确定没走索引。MySQL 的 DELETE 分两步:先定位行(类似 SELECT),再加锁删除;这一步定位失败,后面全白搭。
常见诱因包括:
-
status列上根本没建索引 - 建了索引但类型不匹配,比如
status是VARCHAR,却写了WHERE status = 1(隐式转换) - 用了函数,比如
WHERE UPPER(status) = 'PENDING' - 联合索引是
(created_at, status),但 WHERE 只写了status = 'pending'(最左前缀失效)
IN 子查询在 DELETE 中大概率失效
DELETE FROM t1 WHERE id IN (SELECT id FROM t2 WHERE uid = 1) 这种写法,在 SELECT 里可能走索引,但在 DELETE 里经常退化为全表扫描 t1。MySQL 5.7+ 对 DELETE + IN 子查询的执行计划生成逻辑和 SELECT 不同,子查询容易被当作非关联子查询延迟执行,导致外层无法用索引驱动。
更可靠的写法是改用 JOIN:
DELETE t1 FROM t1 JOIN t2 ON t1.id = t2.id WHERE t2.uid = 1
这种结构让优化器能明确使用 t2.uid 索引定位,再反向驱动 t1.id 删除,通常稳定走索引。
强制索引(FORCE INDEX)只是绕过问题,不是解决问题
加 FORCE INDEX (idx_status) 确实能让 DELETE 走指定索引,比如:DELETE FROM t FORCE INDEX (idx_status) WHERE status = 'pending'。但它不改变底层事实——可能是索引选择性太差(比如 status 只有 'pending'/'done' 两种值)、统计信息过期、或数据分布导致优化器原本判断全表扫描确实更快。
真正该做的是:
- 用
SHOW CREATE TABLE t核对字段类型,再比对 SQL 中字面量是否一致(字符串必须加引号) - 运行
ANALYZE TABLE t更新统计信息 - 检查该索引是否真的覆盖查询需求,比如高基数字段才适合单列索引
IS NULL 或空值处理不当也会触发全表扫描
如果 WHERE 条件是 WHERE col IS NULL,而 col 上的索引允许 NULL(比如没定义 NOT NULL),B+ 树索引默认不存储全 NULL 值的记录,优化器就只能全表扫描来确保不漏掉任何一行。
验证方式:
- 查索引定义:
SHOW INDEX FROM t WHERE Column_name = 'col' - 看
Null列是否为YES;如果是,且业务允许,可考虑加NOT NULL约束并补默认值 - 否则,避免直接
IS NULL查询,改用标记位(如is_deleted TINYINT DEFAULT 0)替代

















