重复索引拖慢insert是因为每条insert需同步更新所有匹配索引页,两个结构相同的索引(如idx_email和idx_email_dup)导致b+树写入、i/o、缓冲池压力与锁竞争均翻倍,实测批量插入吞吐量可降30%~50%。

重复索引为什么拖慢 INSERT
因为每条 INSERT 都要同步更新所有匹配的索引页。两个结构完全一样的索引(比如 idx_email 和 idx_email_dup),MySQL 会分别写入两份 B+ 树数据——磁盘 I/O 翻倍、缓冲池压力翻倍、锁竞争也更激烈。实测中,单表有 3 个冗余索引时,批量插入吞吐量可能下降 30%~50%。
这不是“多占点空间”的小问题,而是写路径上实实在在的双倍开销。尤其在高并发写入场景下,INSERT 延迟抖动明显,SHOW PROCESSLIST 中常看到大量 updating index 状态。
用 pt-duplicate-key-checker 找出冗余索引
Percona Toolkit 的 pt-duplicate-key-checker 是目前最可靠的自动化检测工具,它比手写 information_schema.STATISTICS 查询更准——能识别列顺序、前缀长度(sub_part)、排序方向(collation)是否真正一致。
使用前确保:
-
pt-duplicate-key-checker已安装(推荐 v3.5+) - 连接用户有
SELECT权限访问information_schema和目标库 - 避免在业务高峰运行,它会扫描索引元数据,对大库可能有轻微负载
执行命令:
pt-duplicate-key-checker --host=localhost --user=root --password=xxx --databases=mydb
输出中带 duplicate 或 redundant 标签的行,就是可删的索引。注意它不会自动删,只提示风险。
手动确认并删除前的关键检查项
别急着 DROP INDEX,先交叉验证三件事:
- 查
SHOW CREATE TABLE table_name,确认待删索引是否被任何外键或约束隐式依赖(虽然少见,但FOREIGN KEY有时会绑定特定索引名) - 跑
SELECT COUNT(*) FROM information_schema.STATISTICS WHERE table_schema='mydb' AND table_name='users' AND index_name IN ('idx_email', 'idx_email_dup');,核对两个索引的seq_in_index、column_name、sub_part是否逐列相同 - 用
EXPLAIN FORMAT=TREE SELECT * FROM users WHERE email='a@b.com';分别在删前观察优化器是否真用了这两个索引中的某一个;如果两个都从没被选中过,说明它们本就是“死索引”
删除语句必须明确指定索引名:
ALTER TABLE users DROP INDEX idx_email_dup;
删完不等于万事大吉
冗余索引清理只是起点。真正容易被忽略的是:删掉一个索引后,INFORMATION_SCHEMA.STATISTICS 里的统计信息不会立刻刷新,Cardinality 可能仍为旧值,导致后续 EXPLAIN 判断失准。建议紧接着对表做一次快速采样更新:
ANALYZE TABLE users;
另外,如果该表近期经历过大量写入,B+ 树页可能已碎片化——此时即使删了冗余索引,性能提升也可能被碎片抵消。需结合 innodb_buffer_pool_pages_data / innodb_buffer_pool_pages_total 和查询延迟波动综合判断是否需要后续碎片整理。











