delete不走索引本质是优化器评估后认为全表扫描代价更低;执行分定位行和加锁删除两步,where条件无索引时必全表扫描;in子查询在delete中常失效,改写为join更可靠;函数或隐式转换会使索引失效;应优先分析explain而非强制索引。

DELETE不走索引,本质是优化器放弃使用索引
MySQL的DELETE语句执行分两步:先定位行(类似SELECT),再加锁删除。如果WHERE条件列没走索引,优化器就只能全表扫描——哪怕只删1行,也要读完整张表。这不是“懒得走索引”,而是评估后认为全表扫描代价更低。比如EXPLAIN DELETE FROM t WHERE status = 'pending'显示type: ALL、key: NULL、rows接近总行数,就是典型信号。
IN子查询在DELETE里常失效,但SELECT却走索引
这是最易踩坑的场景。例如DELETE FROM t1 WHERE id IN (SELECT id FROM t2 WHERE uid = 1),即使t2.uid有索引、t1.id也有索引,DELETE仍可能全表扫描t1。原因在于:MySQL 5.7对DELETE ... IN (subquery)的执行计划生成逻辑与SELECT不同,子查询可能被当作“非关联子查询”延迟执行,导致外层无法利用索引驱动。
- 用
EXPLAIN查DELETE本身,别只看SELECT——两者执行计划可能完全不同 - 改写为JOIN语法:
DELETE t1 FROM t1 JOIN t2 ON t1.id = t2.id WHERE t2.uid = 1,通常能稳定走索引 - 避免嵌套
IN,尤其当子查询返回结果较多时,优化器更倾向全表扫描
函数/隐式转换让索引彻底失效
只要WHERE条件对索引列做了任何运算或类型转换,索引就作废。比如gmt_created是DATETIME类型,WHERE TO_DAYS(gmt_created) 必然全表扫描;又如<code>status是VARCHAR,但传入数字WHERE status = 1,会触发隐式转换,索引失效。
- 正确写法:
WHERE gmt_created - 确认参数类型一致:
WHERE status = 'pending'(字符串值必须加引号) - 用
SHOW CREATE TABLE核对字段类型,再比对SQL中字面量类型
强制索引不是万能解,反而可能掩盖真问题
FORCE INDEX确实能让DELETE走指定索引,比如DELETE FROM t FORCE INDEX (idx_status) WHERE status = 'pending'。但它只是绕过优化器判断,并不解决根本原因——比如索引选择性差、统计信息过期、或数据分布导致索引实际效率不如全表扫描。
- 先运行
ANALYZE TABLE t更新统计信息,再看是否还失效 - 检查该索引是否覆盖查询:单列索引
idx_status对WHERE status = ?有效,但对WHERE status = ? AND create_time > ?就不够,需联合索引 - 生产环境慎用
FORCE INDEX——它把执行计划绑定死,后续数据增长后可能更慢
真正难的不是让DELETE走索引,而是让优化器“相信”走索引更优。这取决于索引设计、数据分布、统计信息准确性,以及你写的SQL有没有悄悄破坏索引可用性。每次遇到全表扫描,优先查EXPLAIN输出,而不是直接加FORCE。











