MySQL 联合索引怎么用?最左匹配原则到底是什么意思?

上一篇我们讲了 MySQL 索引为什么能加快查询,以及 InnoDB 为什么常用 B+Tree。

现在来看一个实际开发中更常见的问题。

假设有一张用户表:

sql 复制代码
CREATE TABLE user (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50),
    age INT,
    city VARCHAR(50)
);

我们经常需要查询:

sql 复制代码
SELECT *
FROM user
WHERE name = '张三'
  AND age = 20;

这时候应该怎么建索引?

text 复制代码
给 name 建一个索引?

给 age 建一个索引?

还是给 name + age 一起建索引?

这就涉及 MySQL 中非常重要的:

联合索引最左匹配原则

1. 什么是联合索引?

普通索引通常只包含一个字段:

sql 复制代码
CREATE INDEX idx_name
ON user(name);

而联合索引,也叫复合索引,可以包含多个字段:

sql 复制代码
CREATE INDEX idx_name_age
ON user(name, age);

它的意思是:

nameage 作为一组索引键,建立一棵 B+Tree。

注意,不是:

text 复制代码
name 一棵 B+Tree
+
age 一棵 B+Tree

而是:

text 复制代码
(name, age)
    ↓
一棵 B+Tree

这两个字段共同决定索引中的排序方式。

2. 联合索引到底怎么排序?

假设表中有这些数据:

name age
李四 22
张三 25
王五 20
张三 18
李四 30
张三 20

建立:

sql 复制代码
CREATE INDEX idx_name_age
ON user(name, age);

以后,可以把索引的逻辑顺序理解成:

text 复制代码
李四, 22
李四, 30

张三, 18
张三, 20
张三, 25

王五, 20

这里为了方便理解,假设字符串按照上面展示的顺序排列;真实 MySQL 中字符串的比较顺序由字符集和排序规则决定。

最重要的是:

先按照 name 排序,name 相同的时候,再按照 age 排序。

也就是:

text 复制代码
第一排序字段:name

第二排序字段:age

这就是联合索引最核心的结构。

3. 为什么 name = '张三' 可以使用联合索引?

假设执行:

sql 复制代码
SELECT *
FROM user
WHERE name = '张三';

因为索引首先按照 name 排序:

text 复制代码
李四, 22
李四, 30

张三, 18  ←
张三, 20  ←
张三, 25  ←

王五, 20

所有:

text 复制代码
name = '张三'

的记录在索引中形成一个连续范围。

数据库可以通过 B+Tree 快速定位到这个范围,然后顺序扫描。

所以:

sql 复制代码
WHERE name = '张三'

可以很好地利用:

text 复制代码
(name, age)

这个联合索引。

4. 为什么 name + age 一起查询更适合?

现在执行:

sql 复制代码
SELECT *
FROM user
WHERE name = '张三'
  AND age = 20;

索引顺序是:

text 复制代码
张三, 18
张三, 20
张三, 25

数据库先通过:

text 复制代码
name = '张三'

找到对应范围。

然后在这个范围内继续利用:

text 复制代码
age = 20

缩小查找范围。

可以理解成:

text 复制代码
B+Tree
   ↓
定位 name = 张三
   ↓
继续定位 age = 20
   ↓
找到目标记录

所以:

sql 复制代码
WHERE name = '张三'
  AND age = 20

通常可以充分利用:

text 复制代码
idx_name_age

的两个索引列。

5. 那只查询 age 为什么不一样?

现在执行:

sql 复制代码
SELECT *
FROM user
WHERE age = 20;

索引还是:

text 复制代码
李四, 22
李四, 30

张三, 18
张三, 20

王五, 20

你会发现:

text 复制代码
age = 20

的数据并没有因为 age 相同,就集中在一个连续的大范围里。

因为索引首先按照:

text 复制代码
name

排序。

只有:

text 复制代码
name 相同

的时候,才继续按照:

text 复制代码
age

排序。

所以只知道:

text 复制代码
age = 20

数据库无法像查询 name 那样,直接定位到一个简单、连续的索引范围。

这就是最左匹配原则的由来。

6. 什么是最左匹配原则?

对于联合索引:

text 复制代码
(a, b, c)

可以简单理解成:

索引首先按照 a 排序,a 相同再按照 b 排序,a、b 都相同再按照 c 排序。

因此,最容易利用这个索引的查询是:

text 复制代码
a

a + b

a + b + c

例如:

sql 复制代码
CREATE INDEX idx_a_b_c
ON test(a, b, c);

常见情况:

查询条件 是否符合最左前缀
a = 1
a = 1 AND b = 2
a = 1 AND b = 2 AND c = 3
b = 2 不符合
c = 3 不符合
b = 2 AND c = 3 不符合
a = 1 AND c = 3 只具备 a 这个连续前缀

这里要特别注意:

"不符合最左前缀"不等于"数据库绝对不会使用这个索引"。 MySQL 可能进行索引扫描、覆盖索引扫描,或者在特定版本和条件下使用其他优化方式。最左匹配主要描述的是联合索引能否有效用于按键值定位和缩小查找范围。

7. 为什么叫最左?

因为联合索引的字段顺序是:

text 复制代码
(a, b, c)

而不是:

text 复制代码
(c, b, a)

例如:

sql 复制代码
CREATE INDEX idx_name_age_city
ON user(name, age, city);

逻辑排序:

text 复制代码
先 name

name 相同,再 age

name、age 都相同,再 city

所以:

text 复制代码
name

是最左边。

接着:

text 复制代码
name + age

是连续前缀。

再接着:

text 复制代码
name + age + city

也是连续前缀。

但:

text 复制代码
age + city

跳过了最左边的 name,就不再具备同样的定位条件。

8. WHERE 条件顺序会影响最左匹配吗?

这是一个非常常见的误区。

假设有:

sql 复制代码
CREATE INDEX idx_name_age
ON user(name, age);

下面两个 SQL:

sql 复制代码
SELECT *
FROM user
WHERE name = '张三'
  AND age = 20;

以及:

sql 复制代码
SELECT *
FROM user
WHERE age = 20
  AND name = '张三';

很多人会认为第二个 SQL:

text 复制代码
age 写在前面

所以不符合最左匹配。

其实不是。

最左匹配看的是索引字段顺序,不是 SQL 中 WHERE 条件的书写顺序。

MySQL 优化器可以重新组织这些等值条件。

所以这两个 SQL 在通常情况下都可以利用:

text 复制代码
(name, age)

联合索引。

真正重要的是:

text 复制代码
索引定义:

(name, age)

而不是你把:

text 复制代码
name = ...

写在 WHERE 的第几行。

9. 联合索引遇到范围查询会怎么样?

现在假设有:

sql 复制代码
CREATE INDEX idx_a_b_c
ON test(a, b, c);

执行:

sql 复制代码
SELECT *
FROM test
WHERE a = 1
  AND b > 10
  AND c = 3;

这里:

text 复制代码
a = 1

是等值条件。

text 复制代码
b > 10

是范围条件。

索引顺序:

text 复制代码
a → b → c

数据库可以先定位:

text 复制代码
a = 1

再定位:

text 复制代码
b > 10

对应的范围。

但是在这个范围内:

text 复制代码
c = 3

通常无法继续像前面的等值条件那样,把 B+Tree 查找范围进一步缩小成一个连续区间。

可以理解成:

text 复制代码
a = 1
   ↓
定位范围

b > 10
   ↓
继续缩小范围

c = 3
   ↓
通常作为后续过滤条件

这就是经常听到的:

联合索引遇到范围条件后,后面的列通常不能继续用于缩小索引查找范围。

但这里还有一个重要细节。

10. 范围后面的字段是不是完全没用了?

不是。

例如:

sql 复制代码
WHERE a = 1
  AND b > 10
  AND c = 3;

虽然 c 通常不能继续缩小索引定位范围,但它仍然可能用于:

text 复制代码
索引条件下推(Index Condition Pushdown)

也就是:

text 复制代码
先扫描符合 a、b 范围的索引记录
       ↓
在索引层判断 c = 3
       ↓
符合条件的再继续读取完整行

所以不能简单说:

text 复制代码
范围查询后面的索引列全部失效

更加准确的是:

后续列通常不能继续用于范围定位,但仍可能参与索引过滤,甚至用于覆盖索引。

这一点也是面试中比较容易被问到的细节。

11. 为什么联合索引字段顺序很重要?

假设业务经常查询:

sql 复制代码
SELECT *
FROM user
WHERE name = '张三'
  AND age = 20;

同时也经常查询:

sql 复制代码
SELECT *
FROM user
WHERE name = '张三';

那么:

sql 复制代码
CREATE INDEX idx_name_age
ON user(name, age);

通常比较合适。

因为它同时可以服务:

text 复制代码
name

name + age

两类查询。

但如果创建:

sql 复制代码
CREATE INDEX idx_age_name
ON user(age, name);

虽然:

text 复制代码
age + name

一起等值查询也可以很好地利用索引,

但是:

sql 复制代码
WHERE name = '张三'

就不再具备最左前缀。

因此:

联合索引字段顺序应该根据实际查询条件来设计,而不是随便排列。

12. 是不是区分度最高的字段一定放最左边?

也不是。

很多文章会说:

联合索引应该把区分度最高的字段放在最左边。

这只能算一种参考,不能当成绝对规则。

例如业务经常查询:

sql 复制代码
WHERE status = 1
  AND create_time > '2026-01-01';

以及:

sql 复制代码
WHERE status = 1;

那么:

sql 复制代码
(status, create_time)

可能比:

sql 复制代码
(create_time, status)

更适合这些查询。

因为我们需要综合考虑:

text 复制代码
常用查询条件

等值条件和范围条件

ORDER BY

GROUP BY

覆盖索引

字段选择性

所以联合索引设计并不是:

text 复制代码
谁区分度高谁永远放最左边

而应该是:

根据实际 SQL 的过滤、排序和覆盖需求,选择合适的字段顺序。

13. 联合索引还能帮助 ORDER BY

假设有:

sql 复制代码
CREATE INDEX idx_name_age
ON user(name, age);

执行:

sql 复制代码
SELECT *
FROM user
WHERE name = '张三'
ORDER BY age;

因为索引内部:

text 复制代码
name 相同的时候
    ↓
按照 age 排序

所以:

text 复制代码
张三, 18
张三, 20
张三, 25

本身就是有序的。

数据库就有机会直接利用索引顺序:

text 复制代码
定位 name = 张三
       ↓
按照 age 顺序扫描

而不需要额外做一次排序。

这也是联合索引除了加快 WHERE 查询以外,另一个非常重要的作用:

text 复制代码
帮助 ORDER BY

当然,是否最终使用索引顺序,还要看查询条件、排序方向、执行计划等因素。

14. 什么是覆盖索引?

上一篇我们提到过:

text 复制代码
二级索引
   ↓
找到主键
   ↓
回表
   ↓
读取完整行

例如:

sql 复制代码
CREATE INDEX idx_name_age
ON user(name, age);

执行:

sql 复制代码
SELECT *
FROM user
WHERE name = '张三';

如果需要读取:

text 复制代码
id
name
age
city

而二级索引中没有完整的 city 数据,就可能需要回表。

但是如果执行:

sql 复制代码
SELECT name, age
FROM user
WHERE name = '张三';

需要的字段:

text 复制代码
name

age

都已经存在于:

text 复制代码
idx_name_age

中。

那么数据库就可能直接从索引中返回结果:

text 复制代码
二级索引
   ↓
找到 name、age
   ↓
直接返回

不需要再读取完整行。

这就是:

text 复制代码
覆盖索引

简单理解:

查询需要的字段,索引本身已经全部包含了。

15. 联合索引和覆盖索引有什么关系?

联合索引:

text 复制代码
(name, age)

描述的是:

text 复制代码
索引由哪些字段组成

而覆盖索引描述的是:

text 复制代码
某一次查询需要的字段
是否都能从索引中获取

所以:

text 复制代码
联合索引

不等于:

text 复制代码
覆盖索引

但是联合索引经常可以帮助实现覆盖索引。

例如:

sql 复制代码
CREATE INDEX idx_name_age
ON user(name, age);

查询:

sql 复制代码
SELECT name, age
FROM user
WHERE name = '张三';

就有机会:

text 复制代码
使用联合索引
       +
不需要回表

这就是它们之间的关系。

16. 联合索引是不是越多字段越好?

假设我们创建:

sql 复制代码
CREATE INDEX idx_all
ON user(name, age, city, status, create_time);

看起来:

text 复制代码
什么字段都有

是不是很完美?

当然不是。

索引字段越多:

text 复制代码
索引项越大

可能导致:

text 复制代码
一个数据页能容纳的索引项更少

索引占用更多磁盘

INSERT / UPDATE / DELETE 维护成本增加

而且:

text 复制代码
(name, age, city, status, create_time)

也不是所有查询都能充分利用。

例如:

sql 复制代码
WHERE city = '北京';

依然不具备最左前缀。

所以:

联合索引不是把所有字段都塞进去,而是根据常用查询设计一组有价值的字段。

17. 一个实际开发中的索引设计例子

假设订单表:

sql 复制代码
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    status INT,
    create_time DATETIME,
    amount DECIMAL(10, 2)
);

常见查询:

sql 复制代码
SELECT *
FROM orders
WHERE user_id = 10001
  AND status = 1
ORDER BY create_time DESC;

可以考虑:

sql 复制代码
CREATE INDEX idx_user_status_time
ON orders(user_id, status, create_time);

为什么?

因为:

text 复制代码
user_id = 10001
       ↓
先定位某个用户

status = 1
       ↓
继续缩小状态范围

create_time
       ↓
帮助按时间顺序读取

所以这个索引能够同时考虑:

text 复制代码
WHERE 过滤
       +
ORDER BY 排序

当然,实际项目中是否最优,还需要结合数据量、选择性和 EXPLAIN 验证。

但这已经是一种比较典型的联合索引设计思路。

18. 怎么判断联合索引有没有真正用上?

不能只靠自己猜。

MySQL 提供了:

sql 复制代码
EXPLAIN

例如:

sql 复制代码
EXPLAIN
SELECT *
FROM user
WHERE name = '张三'
  AND age = 20;

重点可以先看:

text 复制代码
key

type

rows

Extra

其中:

text 复制代码
key

表示优化器实际选择的索引。

如果看到:

text 复制代码
idx_name_age

说明查询使用了这个索引。

但还需要注意:

key 显示使用了联合索引,不代表所有索引列都用于缩小查找范围。

例如:

sql 复制代码
WHERE a = 1
  AND b > 10
  AND c = 3;

可能使用:

text 复制代码
idx_a_b_c

但并不代表:

text 复制代码
a、b、c

全部都参与了同样程度的索引定位。

所以后面学习 EXPLAIN 时,还需要结合:

text 复制代码
key_len

rows

Extra

实际执行计划

进一步分析。

19. 最左匹配原则最容易记错的三个地方

第一:最左匹配不是 WHERE 书写顺序。

sql 复制代码
WHERE age = 20
  AND name = '张三'

只要索引是:

text 复制代码
(name, age)

并且条件合适,优化器仍然可以利用这个联合索引。

第二:跳过最左列,不代表索引绝对不能被使用。

例如:

sql 复制代码
WHERE age = 20

不符合:

text 复制代码
(name, age)

的最左前缀,但 MySQL 仍可能选择索引扫描等执行方式。

第三:范围后面的字段,不是完全没有作用。

例如:

sql 复制代码
WHERE a = 1
  AND b > 10
  AND c = 3

c 通常不能继续缩小索引范围,但仍可能参与索引条件下推或覆盖索引。

相关推荐
没文化的阿浩1 小时前
【MySQL】用户管理
android·mysql·adb
oradh1 小时前
Oracle enq: US - contention 等待事件总结
数据库·oracle
姚不倒2 小时前
etcd 学习系列(一):从业务需求出发,理解 etcd 是什么
运维·数据库·etcd
susplus2 小时前
【linux应用软件编程】数据库sqlite3
linux·数据库·sqlite
SelectDB2 小时前
Apache Doris + Lance:让多模态数据真正进入智能驾驶与具身智能的分析闭环
数据库
互联网叫兽2 小时前
redis深入学习二
数据库·redis·学习
疯狂打码的少年2 小时前
【数据库技术】复习日:关系代数 + SQL + 规范化(整理对比表)
jvm·数据库·笔记·sql
先吃饱再说3 小时前
从零开发到工程查询:SQLite 嵌入式数据库与高级 SQL 实战
数据库
ShineWinsu3 小时前
对于MySQL:数据库的操作的解析
linux·数据库·c++·mysql·面试·笔试·库的操作