一、索引的介绍
索引本质上就是为"主数据"(表数据)创建的额外"导向数据"。它的核心目的是通过牺牲一定的磁盘空间(存储这些额外的导向结构),来换取极大的查询速度提升(用空间换时间)。
二、 什么是聚簇索引?什么是二级索引?
在 InnoDB 存储引擎中,索引主要分为两种:
-
聚簇索引 (Clustered Index):
-
定义: 决定了表中数据的物理存储顺序 。在 InnoDB 中,数据行本身就是存储在聚簇索引的叶子节点上的。
-
特点: 每个 InnoDB 表有且仅有一个聚簇索引。如果表定义了主键,主键就是聚簇索引;如果没有主键,会选择一个非空且唯一的列作为聚簇索引;如果都没有,InnoDB 会隐式创建一个 6 字节的隐藏列作为聚簇索引。
-
-
二级索引 (Secondary Index / 非聚簇索引):
-
定义: 除了聚簇索引之外的所有其他索引,都属于二级索引(如普通索引、唯一索引、联合索引等)。
-
特点: 一个表可以建立多个二级索引。
-
也就是说聚簇索引,在没有二级索引的情况下: 一般有主键表,聚簇索引就是他的主键(没有的情况也无妨,会有隐藏列作为聚簇索引) 然后对于他的b+树,非叶子节点和叶子节点就是存储主键值(因为整棵树就是根据它构建的嘛) 只是特别的,非叶子节点还会存储指向子节点的指针,而叶子节点则只存储完整的用户行数据。
对于二级索引,一般是搭配着聚簇节点来用, 对于其B+树,同样的,非叶子节点和叶子节点都是存储索引列的值(因为整棵树就是根据它构建的嘛) 特别的,非叶子节点会存储指向子节点的指针,而叶子节点,会存储对应行的主键值
三、什么是回表, 二级索引的回表是在聚簇索引上查找吗?
回表(Table Lookup) ,在数据库(特别是 InnoDB 引擎)中,指的是先通过二级索引找到主键,再拿着这个主键去聚簇索引中查找完整行数据的过程。
数据库中的具体执行流程
假设有一张 students 表,建表语句如下:
sql
CREATE TABLE students (
id INT PRIMARY KEY, -- 主键
name VARCHAR(50), -- 姓名
age INT -- 年龄
);
并且我们给 name 字段加了一个二级索引:CREATE INDEX idx_name ON students(name);。
当你执行查询语句:SELECT * FROM students WHERE name = '张三'; 时,数据库会发生以下动作:
-
第一次查找(走二级索引
idx_name) : 数据库在idx_name的 B+ 树中查找,发现'张三'对应的主键id = 1001。 注意:因为idx_name的叶子节点只存了(name='张三', id=1001),并没有age等其他字段的信息。 -
第二次查找(回表,走聚簇索引) : 数据库拿着刚刚找到的主键
1001,转身去students表的聚簇索引(主键索引)B+ 树上查找。 -
最终拿到数据 : 在聚簇索引的叶子节点上,找到了
id = 1001的完整一行数据(包含id, name, age),然后将结果返回给你。
这**"根据二级索引找到主键,再用主键去聚簇索引查整行数据"**的两步操作,就是回表。
四、为什么要尽量避免回表?
回表会带来额外的磁盘 IO(输入输出)和 CPU 开销。因为你需要去两棵不同的 B+ 树上各查找一次。
-
在普通的小数据量查询中,回表无伤大雅。
-
但在高并发、大数据量的系统中,频繁的回表会严重拖慢数据库的响应速度。
因此,我们在实际开发中,会通过覆盖索引等手段来极力避免回表。
五、覆盖索引
覆盖索引 并不是一种特殊的索引结构(它还是 B+ 树),而是一种查询优化的状态。
当你的查询语句所需要的所有字段 ,都已经包含在某个二级索引的叶子节点中时,数据库就不需要回表去聚簇索引查数据了,直接返回二级索引里的数据即可。这就叫"覆盖了查询需求"。
回顾刚才的 students 表:id (主键), name (二级索引), age。 二级索引 idx_name 的叶子节点里存的是:(name, id)。
❌ 需要回表的情况
sql
SELECT * FROM students WHERE name = '张三';
-- 或者
SELECT id, name, age FROM students WHERE name = '张三';
- 原因: 你需要
age,但idx_name里只有(name, id),没有age。数据库被迫拿着id回去找聚簇索引。
✅ 触发覆盖索引的情况
SELECT id, name FROM students WHERE name = '张三';
-
原因: 你只需要
id和name。巧了!二级索引idx_name的叶子节点里正好存着(name, id)。 -
结果: 数据库在二级索引树上找到数据后,直接就把结果给你了,省去了回表的那一次磁盘 IO。这就是覆盖索引。
六、怎么判断是否使用了覆盖索引?
在使用 EXPLAIN 分析 SQL 语句时,如果 Extra 列出现了以下字样,就说明命中了覆盖索引:
Using index
(注意:如果没有 Using index,而是 Using where,通常意味着发生了回表)
七、 联合索引
联合索引就是把多个字段打包成一棵 B+ 树,按字段顺序逐级排序;查询时必须从左到右连续匹配(最左前缀原则),遇到范围查询则后续字段失效;它最大的实战价值是更容易触发覆盖索引,从而避免回表。
1、联合索引的 B+ 树怎么存?
假设建联合索引 idx_name_age(name, age),它的 B+ 树结构如下:
-
非叶子节点 :存储
(name, age)的组合键值 + 指向子节点的指针。 -
叶子节点 :存储
(name, age)+ 主键 id。
排序规则(核心) :先按 name 排序;name 相同时,再按 age 排序;如果 name 和 age 都相同,最后按主键 id 排序。
2、最左前缀原则(为什么必须从左往右?)
因为 B+ 树的排序是"先 name 后 age"的,只有 name 确定后,age 才有序。
| 查询条件 | 能否走索引 | 原因 |
|---|---|---|
WHERE name = '张三' |
✅ | 命中最左列 |
WHERE name = '张三' AND age = 20 |
✅ | 连续命中两列 |
WHERE age = 20 |
❌ | 跳过最左列 name,age 全局无序 |
WHERE name = '张三' AND age > 20 |
⚠️ | name 走索引,age 是范围查询,后续列失效 |
范围查询截断原则 :一旦遇到 >、<、BETWEEN 等范围查询,该列之后的索引列就无法再利用 B+ 树的有序性了。
3、联合索引字段顺序怎么排?
等值查询字段放前面,范围查询字段放最后。
sql
-- 假设高频查询:WHERE user_id = ? AND status = ? AND create_time > ?
-- ✅ 正确:等值字段在前,范围字段压轴
CREATE INDEX idx_uid_status_time ON orders(user_id, status, create_time);
-- ❌ 错误:范围字段 create_time 放中间,status 会被截断失效
CREATE INDEX idx_uid_time_status ON orders(user_id, create_time, status);