MySQL 索引
面试频率:★★★★★
工作频率:★★★★★
⚡ 30 秒速记
一句话:
索引是一种帮助 MySQL 快速查找数据的数据结构。
InnoDB 索引主要使用:
B+ Tree
核心:
MySQL索引
↓
B+树
↓
树比较矮
↓
减少磁盘 / 数据页访问
↓
提高查询效率
主键索引:
主键B+树
↓
叶子节点
↓
完整行数据
叫:
聚簇索引
二级索引:
普通索引B+树
↓
找到主键ID
↓
主键B+树
↓
找到完整数据
这个过程叫:
回表
一、什么是索引?
可以把索引理解成:
数据库的目录。
例如一本书:
没有目录
↓
从第一页开始找
↓
很慢
有目录:
找到目录
↓
找到页码
↓
直接去对应位置
数据库也是一样。
例如:
SELECT *
FROM user
WHERE id = 9527;
如果没有索引:
可能需要:
从第一条开始
↓
一条一条找
↓
全表扫描
如果:
1000万条数据
效率会很低。
有索引:
通过索引
↓
快速定位数据
所以:
索引的主要作用就是提高数据查询效率。
二、索引是不是越多越好?
不是。
索引可以提高:
SELECT
查询速度。
但是也有成本。
例如:
INSERT INTO user ...
UPDATE user ...
DELETE FROM user ...
修改数据的时候:
数据库可能还需要维护:
索引
所以:
索引越多
↓
INSERT / UPDATE / DELETE
↓
维护索引成本越高
而且索引本身:
需要占用存储空间
所以:
索引不是越多越好,要根据实际查询场景建立。
三、InnoDB 索引主要使用什么数据结构? ⭐⭐⭐⭐⭐
答案:
B+ Tree
例如可以粗略理解:
[30 | 60]
/ | \
/ | \
[10 20] [40 50] [70 80 90]
↓ ↓ ↓
叶子 叶子 叶子
注意:
真正的 B+ Tree 比这个复杂。
目前先理解:
一个节点
可以存很多索引项
所以:
树的高度比较低
四、为什么不用普通二叉树?
普通二叉树:
50
/ \
30 70
/ \
20 80
一个节点通常只有:
两个子节点
如果数据非常多:
树可能比较高
数据库查找:
根节点
↓
下一层
↓
下一层
↓
下一层
↓
...
树越高:
需要访问的数据页可能越多
效率就可能越低。
五、为什么 B+ Tree 适合数据库? ⭐⭐⭐⭐⭐
B+ Tree:
一个节点可以保存:
多个索引项
例如:
[10 | 20 | 30 | 40 | 50]
/ | | \
↓ ↓ ↓ ↓
这样:
一个节点
↓
可以管理更多子节点
所以:
同样1000万条数据
↓
B+树可以保持比较低的高度
数据库查询时:
访问较少的数据页
↓
找到目标数据
所以效率比较高。
六、B+ Tree 第一个特点:树比较矮 ⭐⭐⭐⭐⭐
数据库索引非常关注:
数据页访问次数
假设 B+ Tree:
根节点
↓
中间节点
↓
叶子节点
可能几层:
就可以管理:
大量数据
所以:
数据很多
≠
需要一条一条扫描
而是:
通过B+树
↓
快速定位叶子节点
七、B+ Tree 第二个特点:非叶子节点主要用于导航
例如:
[30 | 60]
/ | \
这里:
30
60
主要作用:
判断下一步往哪里找
例如查:
id = 50
看到:
30 < 50 < 60
于是:
往对应的子节点继续查找
所以:
非叶子节点主要用于索引导航。
八、B+ Tree 第三个特点:数据集中在叶子节点 ⭐⭐⭐⭐⭐
最终查询:
根节点
↓
中间节点
↓
叶子节点
最终:
在叶子节点找到目标记录或记录定位信息
所以:
非叶子节点
↓
主要导航
叶子节点
↓
真正保存对应的数据 / 索引记录
九、B+ Tree 第四个特点:叶子节点有序连接 ⭐⭐⭐⭐⭐
例如:
[1 5 10]
↔
[15 20 25]
↔
[30 35 40]
↔
[45 50 55]
叶子节点:
按照索引顺序排列
+
彼此连接
这个特点特别适合:
范围查询
十、为什么 B+ Tree 适合范围查询?
例如:
SELECT *
FROM user
WHERE id BETWEEN 20 AND 40;
首先:
通过B+树
↓
找到20
然后:
20
↓
25
↓
30
↓
35
↓
40
沿着:
叶子节点
继续扫描即可。
所以:
B+ Tree 不仅适合等值查询,也非常适合范围查询。
十一、什么是聚簇索引? ⭐⭐⭐⭐⭐
InnoDB 中:
主键索引通常就是聚簇索引。
它最大的特点:
主键索引B+树
↓
叶子节点保存完整行数据
例如:
主键索引:
[10 | 20]
/ | \
↓ ↓ ↓
叶子节点:
id=1
name=张三
age=20
id=2
name=李四
age=25
id=3
name=王五
age=30
也就是说:
主键索引叶子节点
↓
就是完整记录
十二、主键查询为什么很快?
例如:
SELECT *
FROM user
WHERE id = 10;
如果:
id是主键
流程:
主键B+树
↓
根据id查找
↓
找到叶子节点
↓
直接获得完整行数据
所以:
主键查询
↓
不需要再去另一个索引找完整数据
十三、什么是二级索引? ⭐⭐⭐⭐⭐
除了聚簇索引以外:
普通建立的索引通常属于:
二级索引
也叫:
Secondary Index
例如:
CREATE INDEX idx_name
ON user(name);
这里:
idx_name
就是二级索引。
十四、二级索引叶子节点存什么?
假设:
id = 主键
建立:
CREATE INDEX idx_name
ON user(name);
二级索引叶子节点可以粗略理解为:
name
+
主键id
例如:
idx_name:
张三 → id=10
李四 → id=20
王五 → id=30
注意:
它通常不是:
张三
id=10
age=20
address=杭州
phone=...
所有字段
而是:
索引列
+
主键
十五、什么是回表? ⭐⭐⭐⭐⭐
这是非常重要的面试题。
假设:
CREATE INDEX idx_name
ON user(name);
查询:
SELECT *
FROM user
WHERE name = '张三';
第一步:
idx_name二级索引
↓
找到张三
↓
得到主键
↓
id = 10
但是:
SELECT *
需要:
完整行数据
而二级索引:
只有:
name + id
怎么办?
再去:
主键索引
查询一次。
流程:
idx_name B+树
↓
找到张三
↓
得到id=10
↓
主键B+树
↓
找到id=10
↓
得到完整数据
这个过程:
叫做回表。
十六、为什么叫“回表”?
可以简单理解:
第一次:
通过二级索引
↓
只拿到主键
数据还不够。
于是:
拿着主键
↓
再回到聚簇索引
↓
找到完整行数据
所以叫:
回表
十七、主键索引 vs 二级索引 ⭐⭐⭐⭐⭐
主键索引
主键B+树
↓
叶子节点
↓
完整行数据
例如:
SELECT *
FROM user
WHERE id = 10;
流程:
主键索引
↓
完整数据
二级索引
二级索引B+树
↓
叶子节点
↓
索引列 + 主键
例如:
SELECT *
FROM user
WHERE name = '张三';
可能:
二级索引
↓
主键ID
↓
主键索引
↓
完整数据
所以:
二级索引查询
可能产生回表
十八、为什么二级索引存主键?
假设:
张三
对应完整数据:
id = 10
name = 张三
age = 20
address = 杭州
...
如果每一个二级索引:
都保存完整数据:
idx_name保存一份完整数据
idx_age又保存一份完整数据
idx_phone又保存一份完整数据
会造成:
大量数据重复
↓
占用大量空间
所以 InnoDB 二级索引:
通常保存:
索引列
+
主键
需要完整数据时:
再通过主键查
十九、一个表有几个聚簇索引?
一个 InnoDB 表:
只有一个聚簇索引
因为:
数据本身只能按照一种聚簇索引结构组织。
但是:
二级索引
可以有多个
例如:
CREATE INDEX idx_name ON user(name);
CREATE INDEX idx_age ON user(age);
CREATE INDEX idx_phone ON user(phone);
可以理解:
user表
├── 聚簇索引
│ └── 主键id
│
├── 二级索引
│ └── name
│
├── 二级索引
│ └── age
│
└── 二级索引
└── phone
二十、没有主键怎么办?
InnoDB 需要选择一个:
聚簇索引
通常可以这样理解:
有主键
↓
使用主键
如果没有主键:
选择合适的唯一非空索引
如果还没有:
InnoDB生成隐藏的行ID
↓
作为聚簇索引键
所以实际开发中:
一般推荐显式创建主键。
二十一、为什么推荐主键不要太大?
因为二级索引:
叶子节点
↓
需要保存主键值
假设一个表:
有5个二级索引
主键越大:
每个二级索引
↓
都要保存更大的主键
可能导致:
索引占用空间增加
所以:
主键通常适合选择比较短、稳定的值。
二十二、完整结构图 ⭐⭐⭐⭐⭐
假设:
user
id name age
1 张三 20
2 李四 25
3 王五 30
主键:
id
普通索引:
name
那么:
user表
│
┌────────┴────────┐
↓ ↓
主键索引 idx_name
↓ ↓
聚簇索引 二级索引
↓ ↓
叶子节点完整数据 叶子节点
↓
name + id
查询:
SELECT *
FROM user
WHERE id = 2;
流程:
主键索引
↓
id=2
↓
完整数据
查询:
SELECT *
FROM user
WHERE name = '李四';
流程:
idx_name
↓
李四
↓
id=2
↓
主键索引
↓
完整数据
后面这个:
二级索引
↓
主键索引
就是:
回表
二十三、为什么 SELECT * 可能增加回表?
例如二级索引:
CREATE INDEX idx_name
ON user(name);
执行:
SELECT *
FROM user
WHERE name = '张三';
二级索引只有:
name
+
id
但是你需要:
*
也就是:
age
address
phone
...
因此:
二级索引数据不够
↓
需要回表
这也是为什么:
实际开发中不建议无脑 SELECT *。
后面学习:
覆盖索引
以后这个问题会更清楚。
二十四、索引完整知识链
目前先掌握:
索引
↓
B+ Tree
↓
为什么使用B+树?
↓
树矮 + 叶子节点有序连接
↓
提高查询效率 + 适合范围查询
然后:
InnoDB索引
↓
┌──────┴──────┐
↓ ↓
聚簇索引 二级索引
↓ ↓
主键索引 普通索引
↓ ↓
完整行数据 索引列+主键
↓
需要完整数据
↓
回表
🎤 面试回答:为什么 MySQL 使用 B+ Tree?
可以回答:
InnoDB 索引主要使用 B+ Tree。B+ Tree 一个节点可以保存多个索引项,因此树的高度比较低,可以减少查询过程中需要访问的数据页数量。
同时 B+ Tree 的叶子节点按照索引顺序组织并相互连接,因此不仅适合等值查询,也非常适合范围查询。
🎤 面试回答:什么是聚簇索引?
可以回答:
InnoDB 的聚簇索引叶子节点保存完整的行记录,通常主键索引就是聚簇索引。
因此通过主键查询时,找到聚簇索引的叶子节点就可以获得完整的数据。
🎤 面试回答:什么是回表?
可以回答:
InnoDB 的二级索引叶子节点通常保存索引列和主键值。
如果查询需要二级索引中没有的字段,就需要先通过二级索引找到主键,再通过主键聚簇索引查询完整行数据,这个过程叫做回表。
🎯 面试追问
Q1:什么是索引?
答:
帮助数据库快速查找数据的数据结构,可以理解成数据库的目录。
Q2:InnoDB 索引主要使用什么结构?
答:
B+ Tree
Q3:为什么 B+ Tree 查询快?
答:
一个节点可以保存多个索引项
↓
树比较矮
↓
减少需要访问的数据页数量
Q4:为什么 B+ Tree 适合范围查询?
答:
因为:
叶子节点有序
+
叶子节点之间相互连接
找到范围起点后:
可以继续顺序扫描。
Q5:什么是聚簇索引?
答:
叶子节点保存完整行记录的索引,InnoDB 通常使用主键索引作为聚簇索引。
Q6:一个表可以有几个聚簇索引?
答:
一个
Q7:一个表可以有多个二级索引吗?
答:
可以
Q8:二级索引叶子节点存什么?
答:
通常:
索引列
+
主键值
Q9:什么是回表?
答:
二级索引
↓
得到主键
↓
主键聚簇索引
↓
查询完整数据
Q10:主键查询需要回表吗?
通常:
不需要
因为:
主键聚簇索引叶子节点
↓
已经保存完整行数据
Q11:二级索引查询一定回表吗?
不一定。
如果二级索引已经包含:
查询所需要的全部数据
就可能:
不需要回表
这就是后面要学的:
覆盖索引
Q12:索引是不是越多越好?
不是。
索引:
提高查询效率
但是:
占用空间
+
增加INSERT / UPDATE / DELETE维护成本
⚠️ 易错点
1. 索引不是越多越好
索引多
↓
查询可能更方便
但是
↓
写操作维护成本增加
2. 二级索引不是一定回表
错误:
二级索引查询
=
一定回表
正确:
查询需要的字段
二级索引已经全部包含
↓
可以不回表
这就是:
覆盖索引
3. 聚簇索引不是单独存一份“索引 + 数据文件”
可以简单理解:
聚簇索引的叶子节点
↓
就是完整行记录
4. B+ Tree 不只是为了等值查询
它还非常适合:
范围查询
排序
顺序扫描
❓ 自测
- 什么是索引?
- 为什么数据库需要索引?
- InnoDB 索引主要使用什么数据结构?
- 为什么 B+ Tree 的树比较矮?
- 树比较矮有什么好处?
- B+ Tree 的非叶子节点主要干什么?
- B+ Tree 的数据主要在哪里?
- 为什么 B+ Tree 适合范围查询?
- 什么是聚簇索引?
- InnoDB 主键索引的叶子节点存什么?
- 什么是二级索引?
- 二级索引的叶子节点通常存什么?
- 什么是回表?
- SELECT * 为什么容易产生回表?
- 主键查询为什么通常不需要回表?
- 二级索引查询一定需要回表吗?
- 一个 InnoDB 表有几个聚簇索引?
- 一个表可以有多个二级索引吗?
- 为什么主键不建议设计得特别大?
- 没有主键时 InnoDB 怎么选择聚簇索引?
- 为什么索引不是越多越好?
MySQL 覆盖索引
面试频率:★★★★★
工作频率:★★★★★
⚡ 30 秒速记
一句话:
覆盖索引就是查询需要的字段,都可以直接从索引中获取,不需要再回表查询。
例如:
CREATE INDEX idx_name ON user(name);
InnoDB 二级索引叶子节点可以简单理解为:
name + 主键id
执行:
SELECT id, name
FROM user
WHERE name = '张三';
需要:
name
id
索引中都有:
idx_name
↓
找到张三
↓
得到name + id
↓
数据已经齐了
↓
直接返回
这就是:
覆盖索引
核心:
覆盖索引
=
索引包含查询需要的数据
=
不用回表
一、先复习什么是回表
假设:
CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(50),
age INT,
address VARCHAR(100)
);
CREATE INDEX idx_name ON user(name);
idx_name 是:
二级索引
它的叶子节点可以简单理解:
name 主键id
李四 → 2
王五 → 5
张三 → 10
注意:
二级索引叶子节点并没有:
完整行数据
二、发生回表的情况
执行:
SELECT *
FROM user
WHERE name = '张三';
第一步:
idx_name B+ Tree
↓
找到张三
↓
得到主键id = 10
但是:
SELECT *
还需要:
id
name
age
address
而 idx_name 中只有:
name
+
id
缺少:
age
address
怎么办?
继续:
idx_name
↓
找到张三
↓
得到id = 10
↓
主键B+ Tree
↓
查找id = 10
↓
找到完整行数据
↓
返回
这个:
通过二级索引找到主键
↓
再通过主键索引查询完整数据
就是:
回表
三、什么是覆盖索引?
现在换一条 SQL:
SELECT id, name
FROM user
WHERE name = '张三';
需要的数据:
查询条件:
name
返回字段:
id
name
而 idx_name 二级索引已经有:
name
+
主键id
所以:
idx_name
↓
找到张三
↓
得到:
name = 张三
id = 10
↓
查询需要的数据已经全部拿到
↓
直接返回
不需要:
再查主键B+ Tree
也就是:
不需要回表
这就是:
覆盖索引。
四、覆盖索引不是一种新的索引类型
这个很容易理解错。
错误理解:
MySQL索引:
├── 主键索引
├── 二级索引
└── 覆盖索引
❌ 不对。
覆盖索引不是一种独立的索引类型。
它描述的是:
某个索引已经包含了当前 SQL 查询需要的全部数据。
所以:
同一个索引
对于不同 SQL:
可能:
SQL A → 覆盖索引
SQL B → 需要回表
五、同一个索引为什么有时候覆盖,有时候不覆盖?
例如:
CREATE INDEX idx_name ON user(name);
SQL 1
SELECT id, name
FROM user
WHERE name = '张三';
需要:
id
name
索引有:
name
id
所以:
✅ 覆盖索引
SQL 2
SELECT id, name, age
FROM user
WHERE name = '张三';
需要:
id
name
age
索引只有:
name
id
缺少:
age
所以:
❌ 无法完全覆盖
需要:
idx_name
↓
拿到id
↓
主键索引
↓
拿age
也就是:
回表
六、联合索引也可以实现覆盖索引
例如:
CREATE INDEX idx_name_age
ON user(name, age);
这个二级索引可以简单理解:
name
+
age
+
主键id
执行:
SELECT id, name, age
FROM user
WHERE name = '张三';
需要:
id
name
age
索引中:
全部都有
所以:
idx_name_age
↓
找到张三
↓
直接拿到:
id
name
age
↓
直接返回
不需要:
回表
所以:
idx_name_age
覆盖了这条SQL
七、为什么二级索引里面有主键?
InnoDB 二级索引:
叶子节点通常保存:
二级索引列
+
主键值
例如:
CREATE INDEX idx_name
ON user(name);
叶子节点可以简单理解:
张三 → id=10
李四 → id=20
王五 → id=30
为什么需要主键?
因为:
如果查询需要完整数据
↓
先找到二级索引
↓
得到主键
↓
根据主键
↓
查询聚簇索引
↓
找到完整行
所以:
二级索引中的主键
↓
就是回表的重要桥梁
八、为什么覆盖索引性能更好?
普通二级索引查询:
第一次:
二级索引B+ Tree
↓
找到主键
第二次:
主键B+ Tree
↓
找到完整数据
也就是:
二级索引
↓
主键
↓
聚簇索引
↓
完整数据
覆盖索引:
二级索引B+ Tree
↓
查询需要的数据已经齐了
↓
直接返回
少了:
回表
所以:
覆盖索引可以减少额外的主键索引查找和数据页访问,通常查询性能更好。
九、覆盖索引和 SELECT * 的关系
例如:
SELECT *
FROM user
WHERE name = '张三';
假设表有:
id
name
age
phone
email
address
create_time
...
而索引:
idx_name
只有:
name
+
id
显然:
索引无法提供所有字段
所以:
必须回表
如果业务实际上只需要:
id
name
却写:
SELECT *
就可能白白增加:
回表
+
数据读取
+
网络传输
所以通常建议:
只查询真正需要的字段,不要无脑 SELECT *。
十、覆盖索引和联合索引
实际开发中:
经常通过:
联合索引
实现:
覆盖索引
例如经常执行:
SELECT age
FROM user
WHERE name = ?;
可以建立:
CREATE INDEX idx_name_age
ON user(name, age);
索引包含:
name
age
主键id
查询:
WHERE需要name
SELECT需要age
全部可以从:
idx_name_age
拿到。
所以:
不用回表
十一、Explain 怎么看覆盖索引?
例如:
EXPLAIN
SELECT id, name
FROM user
WHERE name = '张三';
如果:
Extra
出现:
Using index
通常说明:
查询需要的数据可以直接从索引中取得,不需要再读取完整行数据。
也就是常说的:
使用了覆盖索引
注意:
Using index
和:
Using index condition
不是一个东西。
后面学习:
索引下推 ICP
的时候再详细区分。
十二、回表 vs 覆盖索引
回表
SELECT *
FROM user
WHERE name = '张三'
↓
idx_name
↓
找到:
张三 → id=10
↓
数据不够
↓
主键B+ Tree
↓
找到完整行
↓
返回
覆盖索引
SELECT id,name
FROM user
WHERE name = '张三'
↓
idx_name
↓
找到:
张三 → id=10
↓
数据已经齐了
↓
直接返回
十三、一张图记住
二级索引查询
↓
找到索引对应记录
↓
查询的数据够不够?
↙ ↘
不够 够
↓ ↓
得到主键 直接返回
↓ ↓
查询聚簇索引 覆盖索引
↓
完整数据
↓
回表
十四、完整知识链
现在把前面的 B+ Tree 串起来:
InnoDB索引
↓
B+ Tree
↓
┌──────────────┐
↓ ↓
聚簇索引 二级索引
↓ ↓
叶子节点 叶子节点
完整行数据 索引列 + 主键
↓
查询的数据够?
↙ ↘
不够 够
↓ ↓
回表 覆盖索引
🎤 面试回答
问:
什么是覆盖索引?
答:
覆盖索引是指查询需要的字段都可以直接从索引中获取,不需要再通过主键到聚簇索引中查询完整行数据,因此可以避免回表,提高查询效率。
例如:
CREATE INDEX idx_name
ON user(name);
查询:
SELECT id, name
FROM user
WHERE name = '张三';
由于 InnoDB 二级索引叶子节点包含 name 和主键 id,查询需要的数据都能直接从索引获得,因此不需要回表。
🎯 面试追问
Q1:什么是回表?
答:
先查二级索引
↓
得到主键
↓
再查聚簇索引
↓
获得完整行数据
这个过程叫:
回表
Q2:什么是覆盖索引?
答:
查询需要的数据
↓
索引里面全部都有
↓
直接返回
↓
不需要回表
Q3:覆盖索引是一种新的索引类型吗?
答:
不是
覆盖索引描述的是:
当前索引包含了这条 SQL 查询需要的全部数据。
Q4:为什么覆盖索引快?
答:
因为:
减少回表
↓
减少额外的主键索引查找和数据页访问
Q5:为什么二级索引里面有主键?
答:
因为:
二级索引
↓
找到主键
↓
通过主键
↓
查询聚簇索引
↓
获得完整行
主键是:
回表的重要桥梁
Q6:SELECT * 为什么可能影响性能?
答:
因为:
需要的字段很多
↓
二级索引通常无法全部提供
↓
需要回表
同时还可能增加:
数据读取
网络传输
Q7:联合索引能实现覆盖索引吗?
答:
可以
例如:
CREATE INDEX idx_name_age
ON user(name, age);
查询:
SELECT age
FROM user
WHERE name = ?;
需要的字段:
name
age
索引都有:
可以避免回表
Q8:Explain 怎么判断可能使用了覆盖索引?
答:
看:
Extra
如果出现:
Using index
通常表示:
查询需要的数据可以直接从索引取得
⚠️ 易错点
1. 覆盖索引不是一种索引类型
不要说:
MySQL有:
主键索引
二级索引
覆盖索引
错误。
应该说:
某个索引
↓
刚好覆盖当前查询需要的数据
↓
称为覆盖索引
2. 二级索引不是只存索引字段
InnoDB 二级索引叶子节点还会保存:
主键值
所以:
SELECT id, name
FROM user
WHERE name = ?
即使:
id
没有显式写进:
INDEX(name)
也可能直接从二级索引获得。
3. 使用索引不等于覆盖索引
例如:
SELECT *
FROM user
WHERE name = ?;
可能:
使用idx_name索引
但仍然:
需要回表
所以:
使用索引
≠
一定是覆盖索引
❓ 自测
- 什么是回表?
- 什么是覆盖索引?
- 覆盖索引是一种独立的索引类型吗?
- 为什么二级索引叶子节点要保存主键?
idx_name(name)的叶子节点可以简单理解存什么?- 为什么
SELECT *更容易发生回表? - 为什么覆盖索引通常比回表查询快?
- 联合索引可以实现覆盖索引吗?
SELECT id,name FROM user WHERE name=?为什么可能不需要回表?SELECT id,name,age FROM user WHERE name=?在只有idx_name(name)时为什么可能需要回表?- Explain 中
Using index通常表示什么? - 使用了二级索引是不是就一定没有回表?
MySQL 联合索引与最左匹配原则
面试频率:★★★★★
工作频率:★★★★★
⚡ 30 秒速记
联合索引:
CREATE INDEX idx_name_age_sex
ON user(name, age, sex);
索引顺序:
name → age → sex
核心原则:
联合索引遵循最左匹配原则,要从联合索引最左边的字段开始匹配。
可以先这样记:
INDEX(A, B, C)
WHERE A
→ 可以利用索引
WHERE A AND B
→ 可以利用 A、B
WHERE A AND B AND C
→ 可以利用 A、B、C
WHERE B
→ 无法按照最左前缀正常定位
WHERE C
→ 无法按照最左前缀正常定位
WHERE A AND C
→ A可以用于定位
→ C能否进一步利用要看具体执行计划
注意:
WHERE条件书写顺序
≠
联合索引顺序
例如:
WHERE age = 20
AND name = '张三'
索引:
(name, age)
优化器仍然可以正常利用索引。
一、什么是联合索引?
普通单列索引:
CREATE INDEX idx_name
ON user(name);
只有:
name
一个字段。
联合索引:
CREATE INDEX idx_name_age
ON user(name, age);
包含:
name
+
age
多个字段。
再例如:
CREATE INDEX idx_name_age_sex
ON user(name, age, sex);
就是:
(name, age, sex)
这就是:
联合索引,也叫复合索引。
二、联合索引底层还是 B+ Tree
例如:
INDEX(name, age)
并不是:
name一个B+ Tree
+
age一个B+ Tree
而是:
一个B+ Tree
索引键由:
name + age
共同组成。
可以理解:
B+ Tree
↓
(name, age)
三、联合索引怎么排序?
这是理解:
最左匹配
最重要的一点。
假设数据:
name age
张三 18
张三 20
张三 25
李四 18
李四 30
王五 20
建立:
INDEX(name, age)
索引会按照:
先按照 name 排序,name 相同时再按照 age 排序。
可以简单理解:
张三 18
张三 20
张三 25
李四 18
李四 30
王五 20
也就是:
第一优先级:
name
name相同时:
↓
再按照age排序
四、为什么 name 可以快速查询?
例如:
SELECT *
FROM user
WHERE name = '张三';
索引:
张三18
张三20
张三25
李四18
李四30
王五20
所有:
张三
的数据:
张三18
张三20
张三25
是连续的。
所以 B+ Tree:
可以快速定位张三这一段
因此:
WHERE name = ?
可以很好地利用:
INDEX(name, age)
五、为什么 name + age 也可以?
例如:
SELECT *
FROM user
WHERE name = '张三'
AND age = 20;
查找过程可以简单理解:
先按照name
↓
找到张三这一段
张三18
张三20
张三25
↓
再根据age
↓
找到20
所以:
name → age
符合联合索引:
(name, age)
的顺序。
因此:
name
+
age
都可以用于索引查找。
六、为什么只查询 age 不行?
现在执行:
SELECT *
FROM user
WHERE age = 20;
看看索引:
张三18
张三20 ←
张三25
李四18
李四30
王五20 ←
你会发现:
age
在整个 B+ Tree 中:
不是全局有序的
因为:
age
只有在:
name相同
的情况下才是有序的。
所以:
WHERE age = 20
没办法直接按照:
age
快速定位一个连续范围。
这就是:
最左匹配原则
七、什么是最左匹配原则?
假设联合索引:
INDEX(A, B, C)
索引按照:
A
↓
B
↓
C
进行组织。
查询时:
通常需要从联合索引最左边的字段开始匹配,才能充分利用后面的索引列进行定位。
所以:
A
↓
A + B
↓
A + B + C
都符合:
最左匹配
八、INDEX(A,B,C) 常见情况
假设:
CREATE INDEX idx_abc
ON test(A, B, C);
情况1
WHERE A = ?
符合:
A
所以:
✅ 可以利用联合索引
情况2
WHERE A = ?
AND B = ?
符合:
A → B
所以:
✅ A、B可以用于索引查找
情况3
WHERE A = ?
AND B = ?
AND C = ?
符合:
A → B → C
所以:
✅ A、B、C都可以利用
情况4
WHERE B = ?
缺少:
A
相当于:
A → B → C
↑
最左边断了
所以:
❌ 无法按照联合索引的最左前缀正常定位
情况5
WHERE C = ?
缺少:
A
B
所以:
❌ 无法按照最左前缀正常定位
九、如果中间字段断了怎么办?
索引:
(A, B, C)
查询:
WHERE A = ?
AND C = ?
这里:
A ✅
B ❌
C
可以先通过:
A
定位范围。
但是因为:
B没有确定
所以不能简单按照:
A → B → C
连续缩小 B+ Tree 的搜索范围。
可以简单记:
A
↓
可以用于索引定位
B
↓
断了
C
↓
通常不能像完整连续匹配那样继续缩小搜索范围
但是注意:
不能简单说 C 完全没用了。
MySQL 可能通过:
ICP
索引下推
利用 C:
在索引层进行过滤
所以面试不要说:
B断了以后C一定完全失效
更准确:
A 可以用于索引定位;由于缺少 B,C 通常不能继续用于确定连续的索引查找范围,但可能参与索引层过滤,具体看执行计划。
十、WHERE 条件顺序重要吗?
假设索引:
INDEX(name, age)
SQL:
SELECT *
FROM user
WHERE age = 20
AND name = '张三';
SQL 写的顺序:
age
↓
name
索引顺序:
name
↓
age
是不是索引就失效了?
答案:
不是
MySQL 有:
查询优化器
优化器可以分析条件。
所以:
WHERE age = 20
AND name = '张三'
和:
WHERE name = '张三'
AND age = 20
在这种简单等值条件下:
通常都可以正常利用:
INDEX(name, age)
十一、最左匹配不是 WHERE 的书写顺序
这个一定要分清。
最左匹配说的是:
联合索引内部的字段顺序
不是:
WHERE后面的书写顺序
例如:
索引:
(name, age)
SQL:
WHERE age = 20
AND name = '张三'
虽然:
WHERE:
age → name
但实际条件包含:
name
+
age
优化器可以正常处理。
所以:
WHERE书写顺序
≠
最左匹配顺序
十二、为什么联合索引有顺序?
下面两个索引:
INDEX(name, age)
和:
INDEX(age, name)
不是一回事。
INDEX(name, age)
排序:
先name
↓
name相同再age
适合:
WHERE name = ?
以及:
WHERE name = ?
AND age = ?
INDEX(age, name)
排序:
先age
↓
age相同再name
适合:
WHERE age = ?
以及:
WHERE age = ?
AND name = ?
所以:
联合索引字段顺序非常重要。
十三、联合索引和覆盖索引的关系
假设:
CREATE INDEX idx_name_age
ON user(name, age);
查询:
SELECT age
FROM user
WHERE name = '张三';
这里同时涉及:
最左匹配
+
覆盖索引
第一步:能不能利用索引定位?
条件:
name
索引:
(name, age)
符合:
最左匹配
所以:
✅ 可以利用索引定位
第二步:需不需要回表?
SQL需要:
WHERE:
name
SELECT:
age
索引已经有:
name
age
所以:
查询需要的数据已经齐了
不需要:
回表
这就是:
覆盖索引
十四、最左匹配和覆盖索引不要混淆
最左匹配解决:
能不能利用联合索引有效定位数据?
覆盖索引解决:
找到索引记录以后,需不需要回表?
所以:
最左匹配
↓
能不能很好地找到数据
覆盖索引
↓
找到以后数据够不够
例如:
SELECT age
FROM user
WHERE name = '张三';
索引:
(name, age)
分析:
WHERE name
↓
符合最左匹配
↓
可以利用索引定位
SELECT age
↓
索引中已经存在
↓
覆盖索引
↓
不用回表
十五、为什么联合索引比建很多单列索引更有意义?
例如业务经常查询:
SELECT *
FROM user
WHERE name = ?
AND age = ?;
如果建立:
INDEX(name, age)
可以按照:
name
↓
age
连续定位。
而分别建立:
INDEX(name)
INDEX(age)
它们是:
两个独立的B+ Tree
并不是:
name找到以后
自动进入age索引继续找
虽然 MySQL 某些情况下可能使用:
Index Merge
但不能简单认为:
两个单列索引
=
一个联合索引
所以:
联合索引和多个单列索引不是一回事。
十六、联合索引完整结构
例如:
INDEX(name, age)
可以理解:
B+ Tree
↓
按name优先排序
↓
张三18 → 张三20 → 张三25 → 李四18 → 李四30 → 王五20
↑ ↑ ↑
└── 张三内部age有序 ──┘
所以:
name
↓
全局有序
而:
age
↓
只在name相同的范围内有序
这就是:
最左匹配
产生的根本原因。
十七、一张图记住
假设:
INDEX(A, B, C)
索引结构:
A
↓
A相同时按照B
↓
A、B都相同时按照C
所以:
WHERE A
↓
✅
WHERE A + B
↓
✅
WHERE A + B + C
↓
✅
WHERE B
↓
❌ 缺少最左A
WHERE C
↓
❌ 缺少A、B
WHERE A + C
↓
A可以定位
↓
B断开
↓
C通常不能继续缩小连续搜索范围
十八、完整知识链
现在 MySQL 索引可以串起来:
MySQL索引
↓
B+ Tree
↓
┌───────────────┐
↓ ↓
聚簇索引 二级索引
↓ ↓
完整行数据 索引列 + 主键
↓
查询数据够?
↙ ↘
不够 够
↓ ↓
回表 覆盖索引
再往下:
多个字段建立一个索引
↓
联合索引
↓
INDEX(A,B,C)
↓
按照A → B → C组织
↓
最左匹配原则
最终:
B+ Tree
↓
联合索引
↓
最左匹配
↓
覆盖索引
↓
减少回表
↓
提高查询性能
🎤 面试回答
问:
什么是联合索引和最左匹配原则?
答:
联合索引是由多个字段共同组成的索引,例如
(name, age, sex)。联合索引底层仍然使用 B+ Tree,索引会先按照最左边的
name排序,name相同时再按照age排序,前两个字段相同时再按照sex排序。因此查询通常需要从联合索引最左边的字段开始连续匹配,才能充分利用联合索引进行数据定位,这就是最左匹配原则。
需要注意,最左匹配指的是索引字段的组织顺序,并不是 WHERE 条件的书写顺序,MySQL 优化器可以调整简单查询条件的处理方式。
🎯 面试追问
Q1:什么是联合索引?
答:
多个字段共同建立的一个索引
例如:
INDEX(name, age)
Q2:联合索引底层有几个 B+ Tree?
答:
一个
不是:
name一个B+ Tree
+
age一个B+ Tree
而是:
(name, age)
共同组成一个B+ Tree索引
Q3:INDEX(A,B,C) 怎么排序?
答:
先按照A排序
↓
A相同按照B排序
↓
A、B都相同按照C排序
Q4:什么是最左匹配原则?
答:
联合索引通常需要从最左边的索引字段开始连续匹配,才能充分利用后面的索引列进行数据定位。
Q5:INDEX(A,B,C),WHERE B=? 能正常按照最左前缀定位吗?
答:
不能
因为缺少:
最左边的A
Q6:INDEX(A,B,C),WHERE A=? AND B=? 呢?
答:
可以
符合:
A → B
Q7:INDEX(A,B,C),WHERE A=? AND C=? 呢?
答:
A可以用于索引定位
但是:
B断开
所以 C 通常不能像:
A → B → C
完整连续匹配一样继续缩小搜索范围。
但 C 仍可能:
通过ICP等机制参与索引过滤
具体需要:
EXPLAIN
确认。
Q8:WHERE 条件顺序必须和索引顺序一致吗?
答:
不需要
例如:
INDEX(name, age)
下面:
WHERE age = 20
AND name = '张三'
通常仍然可以使用:
(name, age)
因为:
MySQL优化器会分析查询条件
Q9:INDEX(name,age) 和 INDEX(age,name) 一样吗?
答:
不一样
因为排序顺序不同。
(name,age)
↓
先name
再age
(age,name)
↓
先age
再name
Q10:联合索引和覆盖索引有什么关系?
答:
联合索引可以:
覆盖查询需要的字段
从而实现:
覆盖索引
例如:
INDEX(name, age)
查询:
SELECT age
FROM user
WHERE name = ?;
索引包含:
name
age
所以:
不用回表
Q11:最左匹配和覆盖索引有什么区别?
答:
最左匹配
↓
解决:
能不能利用联合索引有效定位数据
而:
覆盖索引
↓
解决:
找到索引记录以后
还需不需要回表
Q12:两个单列索引等于一个联合索引吗?
答:
不等于
例如:
INDEX(name)
INDEX(age)
是:
两个独立B+ Tree
而:
INDEX(name, age)
是:
一个按照name → age组织的B+ Tree
⚠️ 易错点
1. 最左匹配不是 WHERE 书写顺序
错误:
索引(name,age)
↓
WHERE必须写:
name = ?
AND age = ?
不是。
下面:
WHERE age = ?
AND name = ?
优化器通常一样可以处理。
2. 联合索引不是多个独立索引
INDEX(A,B,C)
不是:
INDEX(A)
+
INDEX(B)
+
INDEX(C)
而是:
一个B+ Tree
3. 中间字段断了,不要说后面的字段完全没用
例如:
INDEX(A,B,C)
WHERE A=? AND C=?
更准确:
A可以用于定位
B断开
C通常不能继续缩小连续索引搜索范围
但可能参与索引层过滤
4. 能走索引不代表一定不用回表
例如:
INDEX(name, age)
执行:
SELECT address
FROM user
WHERE name = '张三'
AND age = 20;
虽然:
name + age
可以利用联合索引定位。
但是:
address
索引中没有。
所以:
仍然需要回表
因此:
能利用索引定位
≠
覆盖索引
❓ 自测
-
什么是联合索引?
-
INDEX(A,B,C)是几个 B+ Tree? -
INDEX(A,B,C)的数据按照什么顺序排列? -
什么是最左匹配原则?
-
为什么
INDEX(name,age)可以快速查询name? -
为什么单独查询
age无法按照最左前缀正常定位? -
INDEX(A,B,C),WHERE A=?可以利用吗? -
INDEX(A,B,C),WHERE A=? AND B=?可以利用吗? -
INDEX(A,B,C),WHERE A=? AND B=? AND C=?可以利用吗? -
INDEX(A,B,C),WHERE B=?呢? -
INDEX(A,B,C),WHERE C=?呢? -
INDEX(A,B,C),WHERE A=? AND C=?怎么分析? -
WHERE 条件书写顺序必须和联合索引一致吗?
-
INDEX(name,age)和INDEX(age,name)一样吗? -
为什么联合索引字段顺序很重要?
-
联合索引和多个单列索引一样吗?
-
最左匹配解决什么问题?
-
覆盖索引解决什么问题?
-
能使用联合索引是不是就一定不用回表?
-
联合索引为什么可以实现覆盖索引?