直接删重复索引可省空间、降写入延迟,但误删会导致查询变慢或失败;须先用information_schema.STATISTICS比对TABLE_NAME、INDEX_NAME、SEQ_IN_INDEX、COLUMN_NAME确认物理重复,再结合EXPLAIN和慢日志验证实际使用情况,避免误删隐性依赖索引。

直接删掉重复索引能省空间、降写入延迟,但误删会导致查询变慢甚至失败——必须先确认哪些真冗余、哪些被实际用着。
怎么用 information_schema.STATISTICS 手动查重复索引
工具只看结构,不看实际使用;pt-duplicate-key-checker 会漏掉前缀包含关系(比如 INDEX(a) 和 INDEX(a,b)),所以得自己查。
- 重点比对
TABLE_NAME、INDEX_NAME、SEQ_IN_INDEX、COLUMN_NAME四个字段:同一张表上,两个索引如果列名和顺序完全一致,就是物理重复 -
INDEX(a,b)和INDEX(b,a)不算重复,顺序不同,B-Tree 索引行为完全不同 - 要识别前缀冗余(如
INDEX(user_id)和INDEX(user_id, status)),得人工判断:如果业务里没有只查user_id的WHERE或ORDER BY,前者大概率可删 - 执行这个语句能快速列出列组合完全一致的索引对:
SELECT s1.TABLE_NAME, s1.INDEX_NAME AS idx1, s2.INDEX_NAME AS idx2 FROM information_schema.STATISTICS s1 JOIN information_schema.STATISTICS s2 ON s1.TABLE_SCHEMA = s2.TABLE_SCHEMA AND s1.TABLE_NAME = s2.TABLE_NAME AND s1.SEQ_IN_INDEX = s2.SEQ_IN_INDEX AND s1.COLUMN_NAME = s2.COLUMN_NAME AND s1.INDEX_NAME != s2.INDEX_NAME WHERE s1.TABLE_SCHEMA = 'your_db' GROUP BY s1.TABLE_NAME, s1.INDEX_NAME, s2.INDEX_NAME HAVING COUNT(*) = ( SELECT COUNT(*) FROM information_schema.STATISTICS s3 WHERE s3.TABLE_SCHEMA = s1.TABLE_SCHEMA AND s3.TABLE_NAME = s1.TABLE_NAME AND s3.INDEX_NAME = s1.INDEX_NAME );
为什么不能直接信 pt-duplicate-key-checker 的输出
它只报「结构等价」索引,比如 UNIQUE(email) 和 INDEX(email) 并存,或两个一模一样的 INDEX(name),但它不会告诉你 INDEX(created_at) 其实从没被任何 EXPLAIN 命中过。
- 它不读慢日志,也不分析查询模式,纯静态比对
- 对 MySQL 8.0+ 隐式主键表(没显式
PRIMARY KEY)可能误判辅助索引是否冗余 - 它默认只比较同类型索引(如都为
BTREE),但如果你有FULLTEXT和普通INDEX混用,它不会交叉比对 - 运行时加
--no-cluster可跳过聚簇索引相关判断,避免在 InnoDB 表上误报「主键列自动附加」导致的假冗余
删之前必须验证的三件事
生产库上删索引不是原子操作,ALTER TABLE 会锁表(MySQL 5.6+ 支持 ALGORITHM=INPLACE,但仍有风险),更关键的是——优化器可能突然选错执行计划。
- 查最近 7 天慢日志,grep 出所有含待删索引名的 SQL(比如
idx_user_id),确认有没有高频慢查依赖它 - 对核心查询跑
EXPLAIN FORMAT=TRADITIONAL,删索引前后各执行一次,对比key字段是否变化、rows是否暴增 - 在从库或影子库上先执行
ALTER TABLE ... DROP INDEX,观察监控里的 QPS、延迟、Buffer Pool 命中率是否异常 - 别一次性删多个——每次只删一个,留足观察窗口(至少 1 小时),再删下一个
ALTER TABLE ... DROP INDEX 的实际执行要点
语法简单,但线上执行容易卡住或失败,尤其大表。
- MySQL 5.6+ 默认支持
ALGORITHM=INPLACE,但需确认innodb_online_alter_log_max_size足够大,否则 DDL 会退化为 COPY - 如果表上有外键约束,删索引前先
SHOW CREATE TABLE看是否被外键引用(比如外键定义里写了FOREIGN KEY (user_id) REFERENCES users(id),而user_id上恰好有索引),删了可能导致外键失效 - 命令本身不带事务回滚能力,一旦开始就停不下来;建议加
LOCK=NONE(如果引擎支持),避免阻塞写入 - 示例:
ALTER TABLE orders DROP INDEX idx_user_id;
注意:索引名是idx_user_id,不是列名user_id
真正难的不是找出重复索引,而是判断某个索引「表面冗余但实际被某条凌晨跑的报表 SQL 依赖着」——这类隐性依赖,只能靠日志+EXPLAIN+业务侧沟通来暴露,工具永远做不到。


















