索引多会让DELETE变慢,因为每删一行InnoDB需同步更新所有相关索引,索引越多写放大越严重,引发B+树分裂、缓存失效和磁盘随机写;优化方法包括分区删除、删冗余索引、主键游标分批等。

为什么索引多会让 DELETE 变慢
每删一行,InnoDB 不只改数据页,还要同步更新所有相关索引(主键、唯一索引、普通索引)。索引越多,写放大越严重——尤其当索引本身也很大时,B+树分裂/合并、缓存失效、磁盘随机写都会拖慢速度。这不是“慢在 SQL”,而是底层物理操作量翻倍了。
删前临时禁用非唯一索引(仅限批量清理场景)
对非唯一普通索引,DISABLE KEYS 能跳过索引构建阶段,大幅减少 I/O。但注意:DISABLE KEYS 对唯一索引和主键无效,且只适用于 MyISAM 表;InnoDB 不支持该语法,必须换思路。
- 真正可行的做法是:先导出需保留的数据 →
DROP TABLE原表 → 重建表(不带非关键索引)→ 导入数据 → 再CREATE INDEX补索引 - 或者更轻量:用
ALTER TABLE t DROP INDEX idx_name删掉几个低频查询用的普通索引,删完再CREATE INDEX回来 - 别碰主键、唯一索引、外键依赖的索引——否则可能触发约束检查失败或数据不一致
用分区表绕过索引维护开销
如果表按时间字段(如 create_time)做了 RANGE 或 LIST 分区,删除整个月/年的数据就变成 ALTER TABLE t DROP PARTITION p202501。这个操作不走行级扫描,也不触碰索引结构,本质是文件系统级删除,毫秒级完成。
- 前提是表已分区,且你要删的是完整分区边界内的数据
- 没分区的表临时加分区成本高,不推荐;已有分区但 WHERE 条件跨多个分区(如
WHERE create_time BETWEEN '2025-01-01' AND '2025-03-15'),仍会触发多分区扫描+索引更新 - 验证是否分区:
SELECT PARTITION_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 't'
分批删 + 主键游标替代 LIMIT OFFSET
用 LIMIT 1000 看似分批,但如果配合 OFFSET(比如 LIMIT 10000,1000),MySQL 仍要跳过前 10000 行,越往后越慢。正确做法是用主键值做游标:
DELETE FROM t WHERE id > 10000 AND id <= 11000 LIMIT 1000;
- 每次记录上一批删到的最大
id,下一批查WHERE id > last_id ORDER BY id LIMIT 1000 - 确保
id有索引(通常是主键),避免全表扫描 - 每批之间加
SLEEP(0.05)或简单延时,缓解日志压力和主从延迟


















