索引并非越多越好,过多会拖慢DML性能、挤占buffer_pool空间;应删除冗余索引、按查询模式设计复合索引顺序,并用覆盖索引优化高频轻量查询。

索引越多查询越快?别信这个误区
索引不是越多越好,mysql 在写入(INSERT、UPDATE、delete)时要同步更新所有相关索引,索引数量多会显著拖慢 DML 性能。更隐蔽的问题是:过多索引会增大 buffer_pool 压力,挤占真正热数据的缓存空间,反而让查询变慢。
常见错误现象包括:
- 单表索引数超过 10 个,且很多是单列冗余索引(比如已有
(a, b),又单独建了a) -
SHOW INDEX FROM table_name显示大量Cardinality极低(接近 0 或远小于表行数)的索引 - 慢查询日志里反复出现
using index condition但实际没走索引,或Using filesort/Using temporary频发
如何识别并删除无效/重复索引
MySQL 8.0+ 可直接用 sys.schema_unused_indexes 视图查长期未被使用的索引;5.7 及以下需结合 performance_schema 手动分析。但更实用的是先做静态扫描:
- 用
pt-duplicate-key-checker(Percona Toolkit)一键检测重复和冗余索引,例如:(user_id)和(user_id, status)共存时,前者通常可删 - 检查
WHERE条件中是否总以某列为前导列,若从不单独查b,只查a = ? AND b = ?或a = ?,那(a, b)足够,不必另建(b) - 唯一性要求高的字段(如
email)优先用UNIQUE约束,它自带索引且语义清晰,避免为“去重”再加普通索引
复合索引顺序怎么排才不踩坑
最左前缀原则不是死规则,而是执行器匹配索引项的路径限制。关键看查询模式中列的过滤强度和排序需求:
- 高选择性列(如
user_id,区分度 > 90%)放最左,能快速缩小扫描范围 - 等值查询列(
=)优先于范围查询列(>、BETWEEN),因为范围之后的列无法用于索引查找,只能用于过滤。例如查询WHERE a = 1 AND b > 10 AND c = 5,索引应为(a, b, c),不是(a, c, b) - 如果含
ORDER BY,且无filesort,需确保排序字段在索引中连续且方向一致(如ORDER BY a ASC, b DESC要求索引为(a, b)且 MySQL 8.0+ 支持降序索引)
什么时候该用覆盖索引而不是加字段
当查询只涉及少数几个字段,且它们能被一个索引全部包含时,用覆盖索引比回表更高效。但要注意代价:
- 覆盖索引本质是把数据“复制”进索引页,
VARCHAR(500)字段加进索引会让 B+ 树节点急剧膨胀,可能引发更多磁盘 I/O - 优先覆盖高频、轻量字段(如
id,status,created_at),避免覆盖大文本或 json 字段 - 用
EXPLAIN看Extra列是否含Using index—— 这才是真覆盖;若出现Using index condition,说明用了 ICP(索引条件下推),但仍有回表
真正难的不是建索引,而是持续观察:上线后盯紧 Handler_read_next / Handler_read_rnd_next 的增长比例,定期跑 select * FROM sys.statement_analysis 看哪些索引被高频使用、哪些常年沉默。索引不是设好就完事的配置项,它是随业务查询模式演化的活体结构。