一、💡 一句话理解
核心结论
索引就像一本书的目录:没有目录就要一页页翻,有目录就能先找到页码,再直接翻到目标内容。
面试先记住一句话:索引用额外的存储空间和写入成本,换取更快的查询。
二、📚 先看一个生活例子
2.1 没有索引:一本本找书
假设图书馆有 10 万本书,你要找《Java 入门》。
如果图书没有目录,你只能:
第 1 本 → 不是
第 2 本 → 不是
第 3 本 → 不是
……
直到找到《Java 入门》这就像数据库的全表扫描:从第一行开始检查,直到找完所有数据。
2.2 有索引:先查目录
如果图书馆有一份按书名排序的目录:
《Java 入门》 → 图书编号 101
《MySQL 实战》→ 图书编号 205
《Redis 设计》→ 图书编号 309现在找书分成两步:
- 在书名目录中找到《Java 入门》,得到图书编号
101。 - 根据编号
101,找到这本书的完整信息和位置。
2.3 图书馆和 MySQL 的对应关系
| 图书馆 | MySQL |
|---|---|
| 一本书的完整信息 | 表中的一行数据 |
| 书名目录 | 普通索引或二级索引 |
| 图书编号 | 主键 |
| 先查目录,再根据编号找书 | 先查二级索引,再回表 |
| 一本本翻找 | 全表扫描 |
三、⚙️ 索引到底怎么工作
3.1 先看完整关系
先不要把几个名词混在一起:
| 名称 | 白话理解 | 主要作用 |
|---|---|---|
| MySQL | 整个数据库软件 | 接收 SQL,管理数据库和表 |
| 存储引擎 | MySQL 内部负责实际存取数据的模块 | 决定数据、索引、事务和锁怎么管理 |
| InnoDB | MySQL 默认使用的存储引擎 | 管理表数据,支持事务、行锁和崩溃恢复 |
| InnoDB 表 | 交给 InnoDB 管理的表 | 表的数据和索引由 InnoDB 保存 |
| 索引 | 表的快速查询目录 | 减少需要扫描的数据 |
| B+Tree | 组织常见索引的数据结构 | 让 InnoDB 快速定位和范围查找 |
把它们连起来就是:
MySQL
└── 数据库
└── 表
└── 由 InnoDB 存储引擎管理
├── 保存表数据
└── 建立索引
└── 常见索引使用 B+Tree 组织执行一条查询时,大致过程是:
SQL 发给 MySQL
→ MySQL 分析 SQL 并选择执行方案
→ InnoDB 根据索引查找数据
→ B+Tree 帮助定位数据页
→ 查询结果返回给 MySQL一句话记忆
MySQL 是整个系统,InnoDB 是负责存取表数据的模块,索引是查询目录,B+Tree 是组织这份目录的方法。
3.2 索引和 B+Tree 有什么关系
先把两个概念分开:
| 概念 | 白话理解 | 解决什么问题 |
|---|---|---|
| 索引 | 数据库里的目录 | 用来加快查询 |
| B+Tree | 组织这份目录的方法 | 让 MySQL 能快速定位和范围查找 |
可以类比成一本书:
索引 = 书后面的目录
B+Tree = 目录按照什么结构排列所以两者不是同一个东西。索引是要实现的功能,B+Tree 是实现索引的一种数据结构。
MySQL 可以使用不同结构实现不同索引。在常见的 InnoDB 表中,主键索引、普通索引和唯一索引通常使用 B+Tree;全文索引和空间索引则使用其他结构。
为了加快查询
→ 建立索引
→ 索引需要一种便于查找的数据结构
→ InnoDB 常用 B+Tree因此,下面先认清常见索引名称,再了解 B+Tree 长什么样。
3.2.1 常见索引类型
先记住:索引可以从不同角度分类,所以这些名称不是完全并列的。
| 分类角度 | 常见类型 | 这个角度在问什么 |
|---|---|---|
| 按叶子节点存什么 | 聚簇索引、二级索引 | 完整行数据放在哪里 |
| 按作用和限制 | 主键索引、普通索引、唯一索引、全文索引、空间索引 | 这个索引用来做什么 |
| 按使用几列 | 单列索引、联合索引 | 一个索引包含几列 |
面试最常问下面几种:
| 索引名称 | 大白话理解 |
|---|---|
| 主键索引 | 根据主键找数据;主键不能重复,也不能是空值。在 InnoDB 中,它通常也是聚簇索引 |
| 普通索引 | 只负责加快查询,不要求索引值唯一,例如给书名建立索引 |
| 唯一索引 | 一边加快查询,一边限制非空索引值不能重复,例如用户手机号 |
| 联合索引 | 把多列合在一个索引中,例如 (类别, 作者, 书名) |
| 全文索引 | 从大段文字中搜索词语,类似文章内容搜索 |
| 空间索引 | 查询地图坐标、区域等空间数据,普通业务较少使用 |
还有两个名字会经常出现:
- 聚簇索引:叶子节点直接保存完整行数据。InnoDB 表通常只有一个,后面会单独讲。
- 二级索引:聚簇索引以外的索引。普通索引、唯一索引和联合索引通常都属于二级索引。
例如,(手机号) 可以同时是“单列索引 + 唯一索引 + 二级索引”;(类别, 作者) 可以同时是“联合索引 + 普通索引 + 二级索引”。它们并不冲突,只是分类角度不同。
面试先记住
最常见的是主键索引、普通索引、唯一索引和联合索引。聚簇索引与二级索引是在说明数据怎么存,不要和前面四种硬分成同一组。
3.3 B+Tree 的数据结构长什么样
B+Tree 这个名字可以先不记。先看 MySQL 怎么从 9 个编号里找到 50。
假设一页最多只放 3 个编号,MySQL 会把排好序的编号分成三组:
A 组:5、10、20
B 组:30、40、50
C 组:60、70、80然后再准备一张“总目录”,记录每个范围应该去哪个组:
| 要找的编号 | 应该去哪一组 |
|---|---|
| 小于 30 | A 组 |
| 30~59 | B 组 |
| 大于等于 60 | C 组 |
现在执行下面的查询:
SELECT * FROM book WHERE id = 50;只需要三步:
用文字再走一遍:
- 要找的编号是
50。 - 总目录告诉我们:
50属于30~59,应该去 B 组。 - B 组只有
30、40、50,很快就能找到50。
如果没有总目录,就要从 A 组开始,把 5、10、20、30、40、50…… 一个个比较。
现在再把例子换成专业名称:
| 刚才的例子 | B+Tree 中的名称 | 作用 |
|---|---|---|
| 总目录 | 根节点 | 告诉 MySQL 下一步应该去哪一组 |
| A、B、C、D 数据组 | 叶子节点 | 保存真正的索引记录 |
| 总目录放不下所有分组信息,再增加分目录 | 中间节点 | 数据特别多时,继续缩小范围 |
所以这棵简化的 B+Tree 实际上就是:
查找 50 时只走这条路线:总目录 → 分目录 1 → B 组 → 找到 50。其他分目录和数据组不用检查。
只需要记住
B+Tree 就是“总目录 + 分组数据”。查询时先用总目录确定去哪一组,再到那一组里找具体数据。数据特别多时,会在总目录和数据组之间再增加几层目录。
上面的数字是为了理解而设计的简化案例。真实 MySQL 的一个节点能够保存很多索引值,但查找思路是一样的。
3.3.1 数据库具体怎么分
先别想数据库,可以把它想成一个放号码卡的仓库:
- 索引记录:一张张排好顺序的号码卡。
- 数据页:装号码卡的盒子。InnoDB 的数据页默认是 16KB。
- 上层目录:记录“从哪个号码开始,要去哪个盒子找”。
假设每个盒子最多放 4 张卡片,现在有:
数据页 A:[10、20、30、40] ← 已经装满这时再放入 50,A 已经塞不下了。MySQL 会拿一个新盒子 B,把这批卡片分开放:
数据页 A:[10、20]
数据页 B:[30、40、50]多出 B 后,目录也要记住它:
10 → 去数据页 A
30 → 去数据页 B以后再有新的数据页,目录就继续增加记录。如果目录自己也放满了,在这个简化例子里,MySQL 就把目录也拆成两页,再在上面增加一张“总目录”。
整个过程只要记住四步:
号码卡越来越多
→ 一个数据页装满,拆成两个数据页
→ 上层目录记下新数据页的位置
→ 目录页也满了,再拆目录并增加一层下面这张图,就是把这个过程完整画出来:
一句话记忆
一个数据页满了,通常会由一页拆成两页;每多一个数据页,上层目录就要多记一个位置;目录页没有空间记录新位置时,目录也会分裂,B+Tree 就会长高一层。
真实边界取决于索引值、记录大小、页面剩余空间和插入顺序,不是提前固定好的。
3.4 BTree 与 B+Tree:结构、区别与查找原理
3.4.1 BTree 是什么
**BTree(B 树)是一种多路平衡查找树。**它不是二叉树;“多路”表示一个节点可以连接多个子节点,“平衡”表示所有叶子节点都在同一层。
还是用图书编号理解:一个节点可以保存多个排好序的编号,用这些编号把查找范围分成几段。下面用“编号 → 图书记录”表示一条数据:
[30 → 图书记录]
↙ ↘
[10 → 图书记录,20 → 图书记录] [40 → 图书记录,50 → 图书记录]这是用于理解的简化 B 树:
- 查
30:在根节点就能找到对应的图书记录。 - 查
20:先判断20 < 30,进入左侧子节点,再找到记录。 - 查
50:先判断50 > 30,进入右侧子节点,再找到记录。
**B 树的内部节点和叶子节点都可以保存键及其对应的数据或记录引用。**这里的“数据”不一定是完整行,取决于具体实现。
为什么数据库会考虑这种结构?数据库通常按页读取数据。如果一个节点能放在一个页面中,一次读取就可以拿到多个键,排除大量无关范围。与每个节点最多只有两个子节点的二叉树相比,B 树可以拥有更多分支,通常更矮,从而减少沿树查找时需要访问的页面。
数据增加或删除时,B 树会通过节点分裂、合并或键的调整维持结构,并不是把新数据一直挂在最下面,让某条分支无限变长。
3.4.2 B+Tree 是什么,与 BTree 有什么区别
B+Tree(B+ 树)把数据记录集中到叶子节点,内部节点只保存用于指路的分隔键和子节点指针;叶子节点之间按键的顺序连接。
可以把它理解为“上层只有目录,最底层才放内容”:
[分隔键:30、60]
↙ ↓ ↘
叶子节点: [10、20] ↔ [30、40、50] ↔ [60、70、80]
带记录 带记录 带记录这里约定:小于 30 走左边,30 ≤ 编号 < 60 走中间,大于等于 60 走右边。图中内部节点的 30、60 只是路标,对应记录仍保存在叶子节点中;具体分隔键的表示方式取决于实现。
| 对比点 | B 树 | B+ 树 |
|---|---|---|
| 数据记录放在哪里 | 内部节点和叶子节点都可以保存 | 集中在叶子节点 |
| 内部节点做什么 | 指路,也可以直接找到数据记录 | 只保存分隔键和子节点指针,负责指路 |
| 单条查询 | 可能在内部节点提前命中 | 需要走到叶子节点才能拿到记录 |
| 叶子节点的连接 | 标准结构不依赖叶子链表 | 叶子节点按键的顺序连接 |
| 范围查询 | 通常需要在树中进行中序遍历 | 定位起点后,沿叶子节点顺序扫描 |
| 相同页面大小下的分支数量 | 内部节点携带数据,通常能容纳的分支较少 | 内部节点更精简,通常能容纳更多分支 |
以查询图书编号 30~70 为例,B+ 树的处理过程是:
从根节点找到 30 所在的叶子节点
→ 读取 30、40、50
→ 沿叶子节点的链接进入下一页
→ 读取 60、70
→ 遇到 80,超过范围,结束扫描这里不需要为每条记录重新从根节点出发。B 树也支持范围查询,只是通常要在树中跨节点遍历,不能说它“无法查范围”。
**InnoDB 采用 B+ 树结构的主要原因:内部节点能容纳更多分支,树通常较矮;叶子节点有序连接,便于范围查询和顺序扫描。**真实查询是否发生磁盘读取,还取决于相关页面是否已经在内存缓存中,不能把树高直接当作每次查询的磁盘 I/O 次数。
两个容易混淆的地方
① B+ 树并不保证所有查询都比 B 树快,B 树的单条查询可能在内部节点提前命中。
② InnoDB 聚簇索引的叶子节点保存完整行,二级索引的叶子节点保存索引列和主键值,所以“叶子节点存数据”不等于“所有索引的叶子都存整行”。
MySQL 官方文档有时用广义的 B-tree 称呼这类索引;面试中讨论 InnoDB 的具体组织方式时,通常称为 B+Tree。不要仅根据文档中的名称,就把它理解成上面“内部节点也保存数据记录”的经典 B 树。
3.4.3 B+Tree 如何一层层缩小范围
数据较少时,一张总目录就够了。真实数据库可能有几百万行,一张目录放不下所有分组信息,于是 MySQL 会增加“分目录”。
每经过一层目录,就会排除大量不相关的数据。这就是“一层层缩小范围”。
B+Tree 适合数据库的原因,面试说出下面三点就够了:
- 每个节点能保存很多目录项,所以树通常不高。
- 查找时逐层缩小范围,减少磁盘读取。
- 最下面的数据按顺序排列,适合范围查询。
3.5 主键索引:编号后面就是完整数据
假设有一张图书表:
book(id, title, author, shelf)叶子节点,就是 B+Tree 最底下的那一层。
把 B+Tree 想成一棵倒过来的树:
最上面:根节点 → 总目录
中间:中间节点 → 分目录
最下面:叶子节点 → 真正存放索引记录的地方之所以叫“叶子”,是因为它已经到了树的末端,下面不会再连接其他节点。上面的根节点和中间节点主要负责带路,最后真正要找的内容在叶子节点中。
对于 InnoDB 的主键索引,叶子节点中放的是“主键 + 完整行数据”。例如:
叶子节点中的一条记录:
101 → Java 入门、张老师、A-03 书架id 是主键。InnoDB 的主键索引叶子节点保存完整行数据,所以通过主键找书时,可以直接拿到书名、作者和书架位置。
3.5.1 聚簇索引是什么
聚簇索引,就是把索引和完整行数据放在一起。
还是用图书馆来理解:普通目录只告诉你书在哪里;聚簇索引更像书架本身已经按照图书编号排好,找到编号时,完整的书也就在这里。
在 InnoDB 的主键索引中,最下面的叶子节点大致可以理解成:
101 → [Java 入门,张老师,A-03]
205 → [MySQL 实战,李老师,B-05]
309 → [Redis 设计,王老师,B-08]左边的 101、205、309 是主键,右边是对应的完整行数据。因此,按照主键查询时,找到主键索引的叶子节点,就已经拿到了完整数据,不需要再去别的地方找一次。
聚簇索引
= 按主键排好的 B+Tree
= 叶子节点直接保存完整行数据一张表的完整数据只能按照一种主要方式组织,所以一张 InnoDB 表通常只有一个聚簇索引。表有主键时,主键索引通常就是聚簇索引。
不同索引的叶子里放的东西不同
主键索引的叶子节点保存完整行数据;普通索引的叶子节点主要保存“索引列 + 主键值”,下一节会接着讲。
3.6 二级索引和回表:先拿编号,再找完整数据
现在给 title 建一个普通索引:
CREATE INDEX idx_title ON book(title);这个书名索引的叶子节点主要保存:
书名 + 主键 id执行下面的查询:
SELECT * FROM book WHERE title = 'Java 入门';查找过程如下:
最后“再去主键索引查一次”的动作,就叫回表。
面试记法
二级索引像书名目录,目录里记录了图书编号;根据编号再去拿完整图书信息,就是回表。
3.7 覆盖索引:目录里的信息已经够了
如果只查询主键:
SELECT id FROM book WHERE title = 'Java 入门';书名索引中已经有 title 和 id,可以直接返回结果,不需要再去主键索引拿完整行。这就是覆盖索引。
一句话区分:
- 需要索引之外的列:回表。
- 查询需要的列都在索引中:覆盖索引,不回表。
3.8 联合索引:像按“类别、作者、书名”排目录
假设建立联合索引:
CREATE INDEX idx_category_author_title
ON book(category, author, title);它的排列方式是:
因此下面这些条件容易利用该索引查找:
WHERE category = '编程'
WHERE category = '编程' AND author = '张老师'
WHERE category = '编程'
AND author = '张老师'
AND title = 'Java 入门'但如果只查:
WHERE author = '张老师'就像你拿到的是一份“先按类别排列”的目录,却跳过类别直接找作者。不同类别下都可能有张老师,只能到处翻,无法很好地利用这份目录快速定位。
这就是最左前缀原则:联合索引 (a, b, c) 通常要从最左边的 a 开始使用。
3.9 为什么不能给每列都建索引
目录也需要维护。每新进一本书,不但要把书放到书架,还要更新书名目录、作者目录、类别目录。
数据库也是一样:
- 索引会占用磁盘空间。
- 新增、修改、删除数据时,也要更新索引。
- 索引越多,写入成本越高。
所以不是索引越多越好,而是要为经常查询、确实能减少扫描的条件设计索引。
四、🎤 面试怎么回答
4.1 什么是索引
索引类似书的目录,是数据库为加快查询建立的有序数据结构。没有索引可能需要全表扫描;有索引可以快速缩小查找范围。它会占用额外空间,增删改时也要维护,所以不是越多越好。
4.2 为什么使用 B+Tree
B 树和 B+ 树都是多路平衡查找树。B 树的内部节点也能保存数据记录;B+ 树的内部节点只保存分隔键和指针,数据记录集中在叶子节点。这样内部节点通常能容纳更多分支,树更矮;叶子节点之间又有有序链接,便于范围扫描,所以 InnoDB 使用 B+ 树结构。B 树的单条查询可能提前命中,不能说 B+ 树在所有场景都更快。
4.3 什么是回表和覆盖索引
InnoDB 的二级索引保存索引列和主键值。先查二级索引拿到主键,再去主键索引拿完整行叫回表;如果查询需要的列在二级索引中已经都有了,就能直接返回,这叫覆盖索引。
4.4 使用联合索引需要注意什么
先按这条路线回答:最左前缀 → 范围条件 → 列的排列顺序 → 覆盖索引和维护成本。
联合索引首先要注意最左前缀原则,例如
(a,b,c)通常可以利用a、a+b、a+b+c来定位数据。其次要注意范围条件,遇到>、<等范围比较后,后面的列通常不能继续缩小同一次索引扫描的范围,但仍可能参与过滤。设计索引时,要根据常用查询来排列字段,综合考虑等值条件、范围条件、排序和字段区分度。最后,可以利用覆盖索引减少回表,但不要为了覆盖所有查询,把索引建得过宽、过多,否则会增加存储和写入成本。
4.4.1 最左前缀:看索引列顺序,不看 WHERE 书写顺序
对于联合索引 (a,b,c),可以理解为先按 a 排序,a 相同再按 b,最后按 c。
| 查询条件 | 通常如何利用索引定位 |
|---|---|
a = 1 | 利用第一列 a |
a = 1 AND b = 2 | 利用连续的前两列 a,b |
a = 1 AND b = 2 AND c = 3 | 利用完整的 a,b,c |
a = 1 AND c = 3 | 通常先利用 a 定位,跳过 b 后,不能直接把 a,c 当成连续前缀 |
b = 2 | 缺少最左列,通常无法按普通最左前缀方式直接定位 |
WHERE b = 2 AND a = 1 与 WHERE a = 1 AND b = 2 都可以利用 a,b。最左前缀约束的是索引定义中的列顺序,不要求把 WHERE 条件按这个顺序书写。
这里说的是常规索引定位方式。跳过最左列不代表数据库绝对不会扫描这个索引,特定情况下还可能使用跳跃扫描等优化;入门时先掌握常规规则。
4.4.2 范围条件:前一列用了 >,后一列通常只能筛选结果
这一条到底要注意什么
联合索引
(班级, 学号)中,如果班级是等值条件,可以继续利用学号定位;如果班级用了>这样的范围条件,学号通常不能继续缩小这次索引扫描的范围,但仍能筛选结果。不要看到两列都写在 WHERE 中,就认为两列都能帮助减少扫描。**会导致什么?**数据库可能需要扫描很多不符合学号条件的索引记录,再把它们过滤掉。例如,扫描 4950 条记录,最后只返回 99 条。查询结果仍然正确,但额外的读取和筛选会增加开销,可能让查询变慢。
**先看档案怎么摆。**档案按“先班级、再学号”排列,而不是把所有班的同一学号放在一起:
1班1号、1班2号、1班3号
2班1号、2班2号、2班3号
3班1号、3班2号、3班3号情况一:找“2 班的 3 号”。
班级已经固定,直接找到 2 班,再按有序的学号定位到 3 号。两个条件都能帮助缩小查找位置。
先定位:2班1号、2班2号、【2班3号】
再定位: 【2班3号】情况二:找“班级大于 1,且学号是 3”的所有档案。
“班级大于 1”先确定从 2 班开始,到这些班级的档案结束。扫描这段档案时,实际遇到的是:
2班1号 → 2班2号 → 2班3号 → 3班1号 → 3班2号 → 3班3号
跳过 跳过 保留 跳过 跳过 保留这里“学号是 3”仍然有用:它决定哪些档案留下。但两个 3 号没有挨在一起,不能用一个更小的连续区间,把其他学号都排除在扫描范围之外。
**这就是要注意的区别:写了“学号是 3”,不代表只需要扫描各班的 3 号。**在这里讨论的普通连续扫描中,仍会遇到其他学号,再把它们筛掉。
**对应到 SQL。**假设学生表 student 有联合索引 (class_no, student_no),以下只分析条件,不需要实际执行:
-- 班级是等值条件:可以继续用学号定位。
SELECT class_no, student_no FROM student
WHERE class_no = 2 AND student_no = 3;
-- 班级是范围条件:先扫描班级大于 1 的区间,再筛选学号。
SELECT class_no, student_no FROM student
WHERE class_no > 1 AND student_no = 3;| 条件 | 班级这一列的作用 | 学号这一列的作用 |
|---|---|---|
class_no = 2 AND student_no = 3 | 定位到 2 班 | 继续定位到 3 号 |
class_no > 1 AND student_no = 3 | 确定班级大于 1 的扫描范围 | 扫描时筛出 3 号,通常不能继续缩小这个范围 |
换回联合索引 (a,b,c),同样的道理是:a = 1 AND b > 10 AND c = 3 中,a、b 确定扫描范围,c 通常负责进一步过滤。不能把“c 不能继续缩小范围”说成“c 完全没用”。
这会导致什么?扫描的数据可能很多,但最后返回的数据很少,查询也就可能更慢。结果仍然正确,影响的是查询成本。
假设学校有 100 个班,每班学号都是 1~50。使用联合索引 (班级, 学号) 查询“班级大于 1,且学号是 3”时:
最终需要:2~100 班各自的 3 号,共 99 条记录。
按前面讲的连续范围扫描方式:
扫描 2~100 班的学生:99 个班 × 每班 50 人 = 4950 条索引记录
→ 检查每条记录的学号
→ 过滤掉其他学号
→ 最后返回 99 条记录也就是说,**虽然只返回 99 条,却可能扫描、检查 4950 条索引记录。**过滤掉的记录也消耗了读取和判断的时间;数据越多,这种额外开销就越值得关注。实际耗时还取决于缓存、记录大小和数据库选择的执行方式,不是扫描量增加多少倍,耗时就一定增加多少倍。
如果索引顺序换成 (学号, 班级),同一学号的学生会集中排列:
学号 1:1班、2班、3班……100班
学号 2:1班、2班、3班……100班
学号 3:1班、2班、3班……100班这时可以先定位到“学号 3”,再找到其中“班级大于 1”的部分,目标记录就连在一起了。在这个简化例子中,可以直接扫描需要的约 99 条索引记录,而不是前面的 4950 条。
为什么要注意范围条件
不是担心查错,而是避免“返回的数据很少,实际扫描的数据却很多”。后面的列能筛选结果,不代表它能减少前面需要扫描的记录。
**设计索引时怎么用这个知识?**如果高频查询恰好是“学号等于某个值,班级大于某个值”,可以考虑 (学号, 班级):先集中找到这个学号,再在其中按班级查范围。但是否调整顺序还要看其他查询,不能只为这一条 SQL 就直接交换索引列。
面试可以这样说
使用联合索引时,要注意范围条件对后续列的影响。例如
(a,b,c)中,a 等值、b 大于某值时,c 通常不能继续缩小扫描范围,但仍可参与过滤。设计索引时,可以结合高频查询考虑把等值条件列放在范围条件列之前。
这里讲的是 > 对普通连续索引扫描的影响,不是所有运算符都能套用同一条口诀;>=、BETWEEN、IN 等条件需要具体分析。
4.4.3 列顺序:按实际查询设计
如果经常按“类别 + 作者”查图书,(category, author) 可以同时支持按类别查,以及按类别和作者查。如果经常只按作者查,这个顺序通常不合适,需要重新考虑查询需求和索引设计。
设计时先看高频查询的过滤条件,再综合考虑范围查询、排序和区分度。**不要机械地认为“区分度最高的列一定放第一位”。**例如查询总是同时对两列做等值匹配时,把高区分度列放前面并不必然更快,还要考虑其他查询能否复用索引前缀。
4.4.4 覆盖与成本:够用就好
如果查询需要的列已经全部在联合索引中,就可能通过覆盖索引避免回表。但多加一列,也会让索引占用更多空间,增加增删改时的维护成本。
已有 (a,b) 时,单独的普通索引 (a) 可能重复,需要结合实际使用判断;如果单列索引承担唯一性约束等职责,就不能仅凭前缀重复直接删除。
面试记忆
左边要连续,范围要分清;列顺序看查询,覆盖要算成本。
五、📌 快速回顾
索引 = 书的目录
全表扫描 = 从第一页开始逐页翻
主键索引 = 编号后面直接放完整数据
二级索引 = 目录中保存“索引列 + 主键”
回表 = 目录拿到主键后,再找完整数据
覆盖索引 = 目录中的信息已经够用,不必回表
联合索引 = 多级目录,要从最左边开始查最适合面试复述的一句话:
索引通过有序的 B+Tree 减少数据扫描;InnoDB 二级索引查完整行可能需要回表,覆盖索引可以避免回表;联合索引要注意最左前缀。
相关笔记:05.MySQL 事务、06.MySQL 隔离级别、08.MySQL 锁、Elasticsearch 入门