联合索引生效条件与失效场景全解析

0 次阅读

联合索引生效条件与失效场景全解析

数据库性能优化过程中,索引设计一直是核心环节。其中,联合索引(Composite Index)由于能够覆盖多个查询条件,被广泛应用于MySQL等关系型数据库中。然而,很多开发人员在实际使用过程中会发现:明明创建了联合索引,查询速度却没有提升,甚至执行计划完全没有使用索引。

造成这种情况的主要原因,是联合索引并不是“创建后必然生效”。它受到索引结构、查询条件顺序、字段选择性、SQL写法等多方面因素影响。理解联合索引的生效条件以及常见失效场景,是数据库性能优化的重要基础。

什么是联合索引

联合索引是指在多个字段上建立的索引,例如:

CREATE INDEX idx_user_name_age 
ON user(name, age);

该索引包含两个字段:

  • name

  • age

从逻辑结构上看,MySQL的B+Tree索引会按照字段顺序组织数据。

对于上述联合索引,数据库实际维护的是类似:

(name, age)

这样的排序结构,而不是两个独立索引。

例如:

张三 18
张三 25
李四 20
王五 30

数据首先按照name排序,当name相同时,再按照age排序。

因此,联合索引是否生效,最核心的原则就是:

查询条件必须符合索引的最左匹配原则。

联合索引生效的核心条件

1. 满足最左匹配原则

联合索引最重要的使用规则就是最左匹配原则(Leftmost Prefix Rule)。

假设存在索引:

CREATE INDEX idx_a_b_c 
ON table(a,b,c);

该索引可以支持:

WHERE a=1

也可以支持:

WHERE a=1 AND b=2

还可以支持:

WHERE a=1 AND b=2 AND c=3

但是:

WHERE b=2

或者:

WHERE c=3

通常无法有效利用该联合索引。

原因在于B+Tree结构无法跳过前面的字段直接定位后面的字段。

可以简单理解为:

(a,b,c)

先找a
再找b
最后找c

如果连a都不知道,数据库无法快速定位b和c的位置。


2. 查询条件包含索引前导列

联合索引中的第一个字段称为前导列。

例如:

CREATE INDEX idx_city_age_gender
ON user(city,age,gender);

其中:

  • city是第一列

  • age是第二列

  • gender是第三列

有效查询:

SELECT *
FROM user
WHERE city='北京'
AND age=20;

因为包含了第一列city。

但:

SELECT *
FROM user
WHERE age=20;

由于缺少city条件,索引利用率会降低。

实际开发中,经常出现“字段都在索引里面,但是索引没使用”的问题,本质就是没有遵循索引字段顺序。


3. 等值查询能够充分利用联合索引

对于联合索引来说,等值查询通常具有较好的索引效果。

例如:

CREATE INDEX idx_order
ON orders(user_id,status,create_time);

查询:

SELECT *
FROM orders
WHERE user_id=1001
AND status='paid';

数据库可以快速定位:

user_id=1001
    ↓
status=paid
    ↓
扫描对应create_time范围

这种查询模式非常适合联合索引。


4. 范围查询需要注意后续字段

联合索引遇到范围条件时,会影响后续字段的使用。

例如:

CREATE INDEX idx_age_name
ON user(age,name);

SQL:

SELECT *
FROM user
WHERE age>20
AND name='Tom';

虽然两个字段都有条件,但是索引通常只能利用:

age>20

name字段无法继续进行精确定位。

原因是:

范围查询会产生一个扫描区间,后面的字段已经无法继续缩小范围。

常见范围条件包括:



  • <

  • BETWEEN

  • LIKE 'abc%'


5. 查询字段顺序不影响索引使用

很多人误认为:

WHERE a=1 AND b=2

必须对应:

WHERE a=1 AND b=2

实际上:

WHERE b=2 AND a=1

同样可能使用联合索引。

因为MySQL优化器会调整条件执行顺序。

但是:

字段是否存在、字段顺序是否符合索引结构,才是关键。


联合索引常见失效场景

1. 跳过第一个索引字段

索引:

INDEX(a,b,c)

SQL:

SELECT *
FROM t
WHERE b=10
AND c=20;

问题:

没有使用a字段。

由于B+Tree按照a排序,数据库无法直接定位b。

优化方式:

增加a条件:

WHERE a=5
AND b=10
AND c=20

或者重新设计索引:

INDEX(b,c)

2. 使用函数导致索引失效

例如:

索引:

INDEX(create_time)

查询:

SELECT *
FROM orders
WHERE DATE(create_time)='2026-01-01';

由于对字段进行了函数处理:

DATE(create_time)

数据库无法直接利用原始索引。

优化方式:

改为范围查询:

WHERE create_time >= '2026-01-01'
AND create_time < '2026-01-02'

3. 隐式类型转换导致索引失效

例如:

字段:

user_id VARCHAR(20)

SQL:

WHERE user_id=10001

数据库需要进行类型转换。

可能导致:

索引列 → 转换

从而无法走索引。

正确方式:

WHERE user_id='10001'

保持字段类型一致。


4. LIKE左侧包含通配符

索引:

INDEX(name)

有效:

WHERE name LIKE '张%'

因为可以定位:

张开头的数据范围

无效:

WHERE name LIKE '%张'

原因:

数据库不知道字符串起点在哪里,只能扫描。


5. 使用OR导致索引利用率降低

例如:

SELECT *
FROM user
WHERE name='Tom'
OR age=20;

如果:

name
age

不是独立合适的索引结构,优化器可能选择全表扫描。

对于复杂OR条件,可以考虑:

  • 拆分SQL

  • 使用UNION ALL

  • 调整索引设计


6. 查询返回大量数据

即使联合索引存在:

WHERE status='正常'

如果匹配数据占比非常高,例如:

90%的数据都是“正常”。

数据库可能认为:

扫描索引后回表成本更高。

于是选择:

全表扫描

这并不是索引失效,而是优化器认为不用索引更快。


联合索引中的字段顺序如何设计

联合索引设计并不是字段越多越好。

通常需要考虑以下原则:

1. 高频查询字段优先

例如:

大量SQL:

WHERE user_id=? 
AND status=?

那么:

INDEX(user_id,status)

比:

INDEX(status,user_id)

更合理。


2. 区分度高的字段优先

选择性越高的字段,过滤效果越明显。

例如:

用户表:

gender
身份证号

身份证号明显比gender区分度高。

因此:

身份证号

更适合作为索引前列。


3. 考虑覆盖索引

例如:

SELECT id,name
FROM user
WHERE age=20;

如果索引:

INDEX(age,id,name)

数据库可以直接从索引获取数据。

这种方式叫覆盖索引,可以减少回表操作。


如何判断联合索引是否真正生效

最常用的方法是:

EXPLAIN SELECT *
FROM user
WHERE name='Tom'
AND age=20;

重点关注:

type

常见情况:

range
ref