pt-duplicate-key-checker能直接发现重复索引,但仅识别结构完全等价或严格冗余的索引(如UNIQUE(a)与INDEX(a)),不识别前缀包含关系(如INDEX(a)与INDEX(a,b)),且不分析查询使用情况,需人工确认执行删除建议。

pt-duplicate-key-checker 能直接发现重复索引吗
能,但必须注意它只检查「物理上冗余」的索引,比如 INDEX(a) 和 INDEX(a,b) 这种前缀包含关系不会被标记为重复;真正被识别的是完全等价或严格冗余的索引,例如同时存在 INDEX(a,b) 和 INDEX(b,a)(顺序不同但字段相同),或者 UNIQUE(a) 与 INDEX(a) 并存。
运行时需确保 Percona Toolkit 已安装,并用实际凭证连接:
pt-duplicate-key-checker --database employees --user root --password 'xxx'
常见误判点:
- 它不分析查询模式,只做结构比对 —— 即使某个索引从没被
EXPLAIN用过,只要结构不冗余就不会报 - 不会警告「缺失索引」或「低效索引」,纯做减法
- MySQL 8.0+ 的隐藏主键(如无显式
PRIMARY KEY的 InnoDB 表)可能影响判断,建议先SHOW CREATE TABLE确认
手动检查重复索引:用 information_schema 查什么
核心是查 information_schema.STATISTICS 表,重点比对三组字段:TABLE_NAME、INDEX_NAME、SEQ_IN_INDEX 对应的列顺序和数量。重复索引的本质是:同一张表上,两个索引覆盖的列集合完全相同(忽略顺序),且类型一致(都是 INDEX 或都是 UNIQUE)。
执行这条语句可列出疑似重复的索引对:
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
)
AND COUNT(*) = (
SELECT COUNT(*) FROM information_schema.STATISTICS s4
WHERE s4.TABLE_SCHEMA = s1.TABLE_SCHEMA
AND s4.TABLE_NAME = s1.TABLE_NAME
AND s4.INDEX_NAME = s2.INDEX_NAME
);
更实用的做法是先导出所有索引定义:
SELECT TABLE_NAME, INDEX_NAME, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) cols FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = 'your_db' GROUP BY TABLE_NAME, INDEX_NAME;
然后人工比对 cols 字段是否重复 —— 这比写复杂 JOIN 更快、更不容易漏掉隐式差异。
删除重复索引前必须确认的三件事
删错索引可能导致慢查询甚至业务中断,务必逐项核对:
- 确认该索引未被任何
FORCE INDEX显式引用 —— 搜索代码库和慢日志里的SELECT ... FORCE INDEX - 用
SHOW INDEX FROM table_name查看Cardinality值,如果一个索引的基数远低于另一个(比如 10 vs 10000),说明它可能已失效,优先删低基数那个 - 检查
INFORMATION_SCHEMA.INNODB_SYS_INDEXES(MySQL 5.6+)中NAME字段是否含GEN_CLUST_INDEX—— 这类是隐式聚簇索引,不能删
删除命令本身很简单:
DROP INDEX idx_name ON table_name;
但建议加 IF EXISTS 防止脚本失败:
DROP INDEX IF EXISTS idx_name ON table_name;
为什么 UNIQUE + INDEX 同列算重复,而 PRIMARY KEY + INDEX 不算
因为 PRIMARY KEY 是约束 + 索引的复合体,其底层实现强制要求唯一性和非空,而普通 INDEX 不具备这些语义。MySQL 允许你同时存在 PRIMARY KEY(id) 和 INDEX(id),虽然物理结构重叠,但优化器会按规则优先使用主键索引,所以工具不视其为冗余。
但 UNIQUE(id) 和 INDEX(id) 就不同:两者都只是 B+ 树索引,且都允许 NULL(除非显式 NOT NULL),功能完全重叠。此时保留 UNIQUE 更合理 —— 它带约束语义,能防止插入重复值,而普通 INDEX 不能。
容易被忽略的细节是:如果表已有 UNIQUE(id),再建 INDEX(id),MySQL 不会报错,但 pt-duplicate-key-checker 会明确提示「Redundant index」并建议删除后者。


















