应先查sys.schema_unused_indexes视图识别长期未被使用的索引,再结合performance_schema.table_io_waits_summary_by_index_usage分析7天访问频次,并用EXPLAIN验证核心SQL是否实际走索引。

查重复索引前先确认哪些索引真有用
很多团队一看到慢查询就加索引,结果堆出一堆没人用的索引。MySQL 8.0+ 的 sys.schema_unused_indexes 视图能直接列出长期没被优化器选中的索引,比手动猜靠谱得多。但要注意:这个视图只统计自上次 FLUSH STATUS 或服务重启后的使用情况,如果刚重启过,数据就是空的。
更稳妥的做法是结合 performance_schema.table_io_waits_summary_by_index_usage 查最近7天的访问频次,再用 EXPLAIN 对照核心 SQL 看实际是否走索引。别只看“有索引”,要看“有没有被用上”。
- 索引名带
_bak、_old、_temp的,90% 是历史残留,先查SHOW CREATE TABLE确认是否还在 DDL 脚本里 - 单列索引和复合索引列前缀重叠时(比如已有
idx_user_id_status,又单独建了idx_user_id),后者大概率冗余 -
ANALYZE TABLE后再查information_schema.STATISTICS,避免因统计信息陈旧误判选择率
建索引前必须跑这三步验证
不是所有字段都配得上一个索引。低区分度字段(比如 status、is_deleted)单独建索引,基本等于给 MySQL 增加写开销还白占空间。先算选择率:SELECT COUNT(DISTINCT status) / COUNT(*) FROM orders; 结果低于 0.01 就别单建。
真正该建的是组合索引,且顺序不能错。比如高频查询是 WHERE tenant_id = ? AND created_at > ?,那索引就得是 (tenant_id, created_at),反过来就废了。
- 用
EXPLAIN FORMAT=TREE(MySQL 8.0.16+)看执行计划里是否出现using_index_condition或using_index,这是真正用上索引的信号 - 检查 WHERE 条件里有没有隐式类型转换,比如
status是VARCHAR,但写成WHERE status = 1,索引直接失效 - 如果有
OR条件(如WHERE a = 1 OR b = 2),即使 a 和 b 各有索引,优化器也可能放弃走索引,优先考虑改写为UNION
用 pt-duplicate-key-checker 扫描真实重复项
人工比对 information_schema.STATISTICS 容易漏掉细节:比如两个索引列相同但 sub_part(前缀长度)不同,或一个用 ASC 一个没声明,默认都是 ASC,其实一样。Percona 的 pt-duplicate-key-checker 专干这事,它会把结构完全等价的索引标出来,连大小写、空格、注释差异都忽略。
运行命令:pt-duplicate-key-checker --user=root --password=xxx --database=mydb。输出里标 duplicate 的可以删,标 redundant 的(比如 (a,b) 存在时 (a) 就是冗余)要结合查询模式判断——如果真有 WHERE a = ? 查询,留着也无妨。
- 别在高峰期跑,工具会锁表读取元数据,大库可能卡几秒
- 输出里的
ALTER TABLE ... DROP INDEX语句只是建议,务必先在测试环境验证是否影响现有 SQL - 注意
UNIQUE和普通INDEX不算重复,哪怕列完全一样——前者承担约束职责,后者只负责加速
上线前强制走索引评审流程
最有效的防重复手段不是技术,是流程。每个新索引上线前,必须提供三项材料:对应的核心 SQL、EXPLAIN 结果截图、以及该索引解决的具体性能问题(比如某接口 P95 从 1200ms 降到 80ms)。没有这三项,DBA 直接拒批。
很多“重复”其实是命名混乱造成的。比如开发 A 建了 idx_order_user_created,开发 B 没搜到,建了 idx_user_order_time,其实列都是 (user_id, order_id, created_at)。统一用 table_col1_col2 命名法,加个内部索引清单 Wiki,比啥工具都管用。
- 禁止在 VARCHAR(255) 字段上直接建全文索引,除非真要
MATCH ... AGAINST;日常LIKE '%xxx%'场景,前缀索引KEY(col(10))更轻量 - UUID、MD5 类无序值做主键时,别再额外建
id自增主键+索引,InnoDB 的聚簇索引已经够乱了 - MySQL 8.0.13+ 支持函数索引,但
LOWER(email)这种表达式建索引后,查询必须写成WHERE LOWER(email) = 'a@b.com'才能命中,业务代码容易漏改


















