在数据库日常使用中,索引是提升查询速度的核心手段,但错误的索引设计反而会成为性能瓶颈。组合索引和冗余索引的处理是索引优化中非常重要的两个环节,需要结合具体的查询场景来制定合理的策略。

什么是组合索引与冗余索引
组合索引指的是由多个列共同组成的索引,和单列索引不同,它可以在多个查询条件同时命中时发挥更好的作用。比如我们有一个用户表,经常需要根据age和city两个字段同时查询,就可以建立(age, city)的组合索引。
冗余索引指的是重复的、或者可以被其他索引完全覆盖的索引。比如已经存在(a, b)的组合索引,再单独建立(a)的单列索引,那么后者的功能完全可以被前者覆盖,就属于冗余索引。
按需建立组合索引的核心原则
1. 遵循最左前缀匹配原则
组合索引的生效遵循最左前缀规则,也就是查询条件必须从索引的最左列开始连续匹配,索引才能被使用。比如建立(col1, col2, col3)的组合索引,以下查询可以命中索引:
- where col1 = ?
- where col1 = ? and col2 = ?
- where col1 = ? and col2 = ? and col3 = ?
而以下查询无法命中索引:
- where col2 = ?
- where col1 = ? and col3 = ?
因此建立组合索引时,要把最常用的查询条件的列放在最左边。
2. 结合查询频率和过滤性排序
对于组合索引的列顺序,优先把过滤性高、查询频率高的列放在前面。过滤性指的是该列的不同值的数量占总行数的比例,比例越高过滤性越好,能快速缩小查询范围。
比如用户表中status字段只有3个值,user_id是唯一值,那么(user_id, status)的组合索引比(status, user_id)的查询效率更高。
3. 覆盖索引优先
如果查询只需要返回索引包含的列,不需要回表查询数据行,这种索引就是覆盖索引,能大幅提升查询效率。建立组合索引时,可以把查询需要的返回列也加入索引,实现覆盖索引的效果。
比如查询语句是select id, name from user where age > 18,建立(age, id, name)的组合索引,就可以直接通过索引返回结果,不需要查询数据表。
减少冗余索引的具体方法
1. 识别现有冗余索引
可以通过数据库的系统表来查询现有索引的情况,以MySQL为例,查询information_schema.statistics表可以看到所有索引的定义:
-- 查询指定表的索引信息
SELECT
TABLE_NAME,
INDEX_NAME,
SEQ_IN_INDEX,
COLUMN_NAME,
NON_UNIQUE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'your_database_name'
AND TABLE_NAME = 'your_table_name'
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
拿到索引列表后,对比索引的列顺序,就可以判断是否存在冗余:如果索引A的列是索引B的前缀,且两者的过滤条件一致,那么索引A就是冗余的。
2. 清理无用索引
对于确定冗余、或者长期没有被查询使用的索引,可以直接删除。删除前可以先通过数据库的慢查询日志、索引使用统计来确认索引是否真的没有被使用。
MySQL中可以通过sys.schema_unused_indexes视图查看未被使用的索引:
-- 查看未被使用的索引 SELECT * FROM sys.schema_unused_indexes;
确认无用后执行删除操作:
-- 删除冗余索引 DROP INDEX index_name ON table_name;
3. 避免重复创建索引
在新增索引前,先检查现有索引是否已经可以满足查询需求,不要盲目创建新索引。比如已经存在(a, b)的组合索引,就不需要再单独创建(a)的索引。
组合索引与冗余索引的平衡实践
在实际业务中,不要为了追求覆盖索引而创建过大的组合索引,过大的索引会占用更多存储空间,同时降低插入、更新、删除的性能。需要结合业务的读写比例来调整:
- 读多写少的场景:可以适当增加组合索引的长度,覆盖更多查询场景,减少回表次数。
- 写多读少的场景:尽量减少索引数量,只保留必要的核心索引,降低写入时的索引维护成本。
可以通过压测来验证索引调整的效果,观察慢查询数量、数据库CPU和IO使用率的变化,逐步优化到最合适的索引方案。
常见误区提醒
误区1:组合索引的列越多越好。实际上列越多,索引的维护成本越高,且超过3-4列的组合索引,后续列的过滤效果会明显下降。
误区2:主键不需要考虑冗余。如果主键是复合主键,也需要评估是否可以被其他索引覆盖,避免不必要的复合主键设计。
误区3:删除冗余索引不会影响查询。删除前一定要确认索引没有被任何查询使用,否则可能导致原本快速的查询变成全表扫描。