qinyelin
发布于 2026-08-26 / 35 阅读
1
0

MySQL 索引

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 不只是为了等值查询

它还非常适合:

范围查询

排序

顺序扫描

❓ 自测

  1. 什么是索引?
  2. 为什么数据库需要索引?
  3. InnoDB 索引主要使用什么数据结构?
  4. 为什么 B+ Tree 的树比较矮?
  5. 树比较矮有什么好处?
  6. B+ Tree 的非叶子节点主要干什么?
  7. B+ Tree 的数据主要在哪里?
  8. 为什么 B+ Tree 适合范围查询?
  9. 什么是聚簇索引?
  10. InnoDB 主键索引的叶子节点存什么?
  11. 什么是二级索引?
  12. 二级索引的叶子节点通常存什么?
  13. 什么是回表?
  14. SELECT * 为什么容易产生回表?
  15. 主键查询为什么通常不需要回表?
  16. 二级索引查询一定需要回表吗?
  17. 一个 InnoDB 表有几个聚簇索引?
  18. 一个表可以有多个二级索引吗?
  19. 为什么主键不建议设计得特别大?
  20. 没有主键时 InnoDB 怎么选择聚簇索引?
  21. 为什么索引不是越多越好?

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索引

但仍然:

需要回表

所以:

使用索引
≠
一定是覆盖索引

❓ 自测

  1. 什么是回表?
  2. 什么是覆盖索引?
  3. 覆盖索引是一种独立的索引类型吗?
  4. 为什么二级索引叶子节点要保存主键?
  5. idx_name(name) 的叶子节点可以简单理解存什么?
  6. 为什么 SELECT * 更容易发生回表?
  7. 为什么覆盖索引通常比回表查询快?
  8. 联合索引可以实现覆盖索引吗?
  9. SELECT id,name FROM user WHERE name=? 为什么可能不需要回表?
  10. SELECT id,name,age FROM user WHERE name=? 在只有 idx_name(name) 时为什么可能需要回表?
  11. Explain 中 Using index 通常表示什么?
  12. 使用了二级索引是不是就一定没有回表?

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

索引中没有。

所以:

仍然需要回表

因此:

能利用索引定位
≠
覆盖索引

❓ 自测

  1. 什么是联合索引?

  2. INDEX(A,B,C) 是几个 B+ Tree?

  3. INDEX(A,B,C) 的数据按照什么顺序排列?

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

  5. 为什么 INDEX(name,age) 可以快速查询 name?

  6. 为什么单独查询 age 无法按照最左前缀正常定位?

  7. INDEX(A,B,C),WHERE A=? 可以利用吗?

  8. INDEX(A,B,C),WHERE A=? AND B=? 可以利用吗?

  9. INDEX(A,B,C),WHERE A=? AND B=? AND C=? 可以利用吗?

  10. INDEX(A,B,C),WHERE B=? 呢?

  11. INDEX(A,B,C),WHERE C=? 呢?

  12. INDEX(A,B,C),WHERE A=? AND C=? 怎么分析?

  13. WHERE 条件书写顺序必须和联合索引一致吗?

  14. INDEX(name,age) 和 INDEX(age,name) 一样吗?

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

  16. 联合索引和多个单列索引一样吗?

  17. 最左匹配解决什么问题?

  18. 覆盖索引解决什么问题?

  19. 能使用联合索引是不是就一定不用回表?

  20. 联合索引为什么可以实现覆盖索引?


评论