Appearance
MySQL 索引详解
这篇文章专门讲 MySQL 里的“索引”,不把它只当成一个概念名词,而是把它放回真实工程问题里来看:
- 为什么索引能加速查询
- 为什么索引也会拖慢写入
- 为什么有些 SQL 明明建了索引还是慢
- 怎样设计索引,才能兼顾查询效率和维护成本
如果你已经看过 MySQL 总览页,可以把这篇文章理解成其中“索引”部分的展开版。
1. 索引到底在解决什么问题
不建索引时,数据库很多查询只能做全表扫描。所谓全表扫描,本质上就是从头到尾把记录一行一行看过去,再判断哪一行符合条件。
当数据量小时,这件事未必明显;但一旦表里有几十万、几百万甚至更多数据,问题就出来了:
- 扫描范围太大
- 排序代价高
- 分组和联表成本上升
- 热点查询会持续吃掉 I/O 和 CPU
索引的核心思路是:提前把“如何快速找到数据”这件事组织好。
它不是凭空让查询变快,而是把原本“扫描很多数据”的过程,变成“沿着一棵有序结构快速定位目标范围”。
2. 没有索引 vs 有索引
mermaid
flowchart LR
A[查询条件<br/>where id = 1001] --> B{有没有索引}
B -- 没有 --> C[从第一行开始扫描]
C --> D[逐行判断条件]
D --> E[可能扫到很后面才命中]
B -- 有 --> F[先走索引结构定位范围]
F --> G[快速找到目标记录附近]
G --> H[再读取目标数据]这一部分最重要的不是形式,而是你要抓住一个本质:
- 没有索引时,数据库更像“把所有记录翻一遍”
- 有索引时,数据库更像“先查目录,再翻到对应页”
3. 🌟 为什么 InnoDB 主流用的是 B+ 树
在 InnoDB 里,最常见的索引结构是 B+ 树。你可以把它理解成一棵“为磁盘和范围查询优化过的多路查找树”。
它适合数据库,不是因为它概念上高级,而是因为它刚好满足数据库最在意的几件事:
- 树要尽量矮,减少 I/O 次数
- 数据要有序,方便范围查询
- 等值查询、排序、范围扫描要尽量统一处理
3.1 B+ 树的关键结构
你不需要一开始就把细节背得很死,只要先记住这三个点:
- 非叶子节点主要存 key 和子指针
- 真正的数据主要落在叶子节点
- 叶子节点之间按顺序连接
3.2 B+ 树索引结构
mermaid
flowchart TD
R[根节点<br/>17、35]
N1[非叶子节点<br/>9]
N2[非叶子节点<br/>26]
N3[非叶子节点<br/>42]
L1[叶子节点<br/>3 6 8]
L2[叶子节点<br/>9 12 16]
L3[叶子节点<br/>17 20 24]
L4[叶子节点<br/>26 30 34]
L5[叶子节点<br/>35 38 40]
L6[叶子节点<br/>42 45 48]
R --> N1
R --> N2
R --> N3
N1 --> L1
N1 --> L2
N2 --> L3
N2 --> L4
N3 --> L5
N3 --> L6
L1 -. 顺序连接 .-> L2
L2 -. 顺序连接 .-> L3
L3 -. 顺序连接 .-> L4
L4 -. 顺序连接 .-> L5
L5 -. 顺序连接 .-> L6这一部分想说明的是:
- 这是一张为了讲清结构而做的简化示意图,不代表真实页大小和阶数
- 上层节点负责“导航”
- 最下面一层叶子节点负责“真正命中数据”
- 叶子节点天然有序,所以
between、> <、排序扫描都很友好
这也是为什么数据库更偏爱 B+ 树,而不是红黑树或者哈希做通用索引。
4. 🌟 聚簇索引、二级索引、回表
这是 InnoDB 索引里最容易让人“背了名词但没真正理解”的一段。
4.1 聚簇索引是什么
在 InnoDB 中,主键索引通常就是聚簇索引。所谓聚簇,不是说它“更高级”,而是说:
表数据本身就是按主键索引组织存储的。
也就是说,聚簇索引的叶子节点里,放的不是“一个地址”,而是整行记录。
4.2 二级索引是什么
二级索引不是按完整行存储的,它的叶子节点通常存的是:
- 二级索引列的值
- 对应记录的主键值
所以当你通过二级索引查到一批主键后,如果 SQL 还需要其他列,MySQL 往往还得再去聚簇索引里把整行捞出来。
这个动作就叫回表。
4.3 🌟 回表流程
mermaid
flowchart LR
A[SQL<br/>select id name age from user where name='张三'] --> B[先查 name 二级索引]
B --> C[二级索引命中<br/>name='张三' -> id=1024]
C --> D{要的列够不够}
D -- 不够 --> E[拿着主键 id=1024]
E --> F[回到聚簇索引]
F --> G[取出整行记录<br/>id name age ...]
D -- 够了 --> H[直接返回结果]“回表”这个词听起来有点抽象,但你可以把它理解成:先通过普通索引找到线索,再根据主键回到主数据里把整行捞出来。
比如有这样一条 SQL:
sql
select id, name, age
from user
where name = '张三';假设表上有 name 索引,但这个索引叶子节点里只保存:
name- 对应记录的主键
id
这时候 MySQL 会先走 name 这个二级索引,找到“张三”对应的主键值,比如 id = 1024。
但问题是,这条 SQL 还要拿 age,而 age 不在这个二级索引里。
所以数据库接下来还得再做一步:拿着 id = 1024,再去聚簇索引里找到这条完整记录。
这第二次回到主键索引拿整行数据的动作,就是回表。
你可以把整个过程简化记成两步:
- 先查二级索引,拿到主键
- 再查聚簇索引,拿到整行
为什么回表会让查询变慢?
因为它本质上多了一次查找过程。如果命中的记录很多,就可能出现:
- 二级索引先扫出很多主键
- 再一条条回到聚簇索引取数据
- 随着数据量上来,随机 I/O 和整体开销都会增加
所以优化里经常会追求覆盖索引,本质上就是:尽量让需要的列直接在索引里拿到,避免回表。
5. 🌟 覆盖索引为什么常常更快
如果一个查询需要的列,恰好都能从索引本身拿到,那么数据库就不必再回到聚簇索引取整行。
这就是覆盖索引。
例如:
sql
select id, name
from user
where name = '张三';如果有一个索引正好包含 name 和 id,那么 MySQL 可能直接从索引结果里把数据返回。
覆盖索引快的根本原因,不是概念上“高级”,而是它少了一次回表。
6. 🌟 联合索引和最左匹配
联合索引是多个列组合成一个索引,例如:
sql
create index idx_user_status_created_at on user(status, created_at, id);这类索引最关键的规则,就是最左前缀匹配。
你可以把它理解成:索引是先按第一列排,再在第一列相同的范围里按第二列排,最后再按第三列排。
6.1 联合索引的排列方式
mermaid
flowchart TD
A[联合索引<br/>status created_at id]
A --> B[先按 status 分大范围]
B --> C1[status = 0]
B --> C2[status = 1]
C1 --> D1[在 status = 0 内<br/>再按 created_at 排序]
C2 --> D2[在 status = 1 内<br/>再按 created_at 排序]
D1 --> E1[最后同值范围内<br/>再按 id 排序]
D2 --> E2[最后同值范围内<br/>再按 id 排序]所以:
where status = 1,可以很好利用索引where status = 1 and created_at > ...,通常也能很好利用索引where created_at > ...,如果跳过了最左列status,利用效果就会明显变差
这就是最左匹配原则的根本原因。不是数据库故意刁难,而是索引本身就是按这个顺序组织的。
7. 🌟 哪些情况会让索引失效或利用变差
“建了索引为什么还是慢”是最常见的问题之一。很多时候不是索引完全失效,而是优化器判断“用索引不划算”或者“只能利用一部分”。
常见情况包括:
7.1 对索引列做函数、计算或隐式转换
例如:
sql
where date(create_time) = '2026-08-01'这种写法相当于先对每行做函数计算,再比较结果,索引往往很难直接利用。
更常见的优化方式是改成范围查询:
sql
where create_time >= '2026-08-01 00:00:00'
and create_time < '2026-08-02 00:00:00'7.2 模糊查询前面带 %
例如:
sql
where name like '%abc'因为前缀不确定,B+ 树很难直接定位起始位置。
但如果是:
sql
where name like 'abc%'通常还能利用前缀有序性。
7.3 返回数据太多
即使建了索引,如果条件命中结果特别多,优化器也可能认为“走索引再回表”不如直接扫表。
7.4 联合索引使用顺序不合理
例如索引是 (a, b, c),结果你的查询大量是:
sql
where b = ? and c = ?这种情况下,索引不是不存在,而是设计和查询模式没有对齐。
8. 索引设计不是越多越好
新手很容易走到另一个极端:一看到 SQL 慢,就继续加索引。
这会带来明显副作用:
- 写入变慢,因为每次插入、更新、删除都要维护索引
- 占用更多磁盘和内存
- 优化器选择变复杂
- 冗余索引会增加维护成本
所以索引设计的原则不是“尽量多”,而是“尽量让高频查询有合适的索引”。
9. 一个更实用的索引设计方法
如果你在真实项目里给表设计索引,可以按这个顺序思考:
- 看高频 SQL,而不是看表结构
- 明确查询条件、排序、分页、返回列
- 判断是等值查询优先,还是范围查询优先
- 让联合索引顺序尽量匹配最常用查询路径
- 尽量让热点查询变成覆盖索引
- 避免重复、重叠、长期不用的索引
10. 怎么把索引这件事讲清楚
如果要把 MySQL 索引真正讲顺,比起一上来背名词,更自然的顺序通常是:
- 先说索引解决的是查询范围缩小问题
- 再说 InnoDB 主流是 B+ 树,因为它适合磁盘和范围查询
- 接着说明聚簇索引、二级索引、回表、覆盖索引
- 再补联合索引和最左匹配
- 最后落到真实项目里如何根据高频 SQL 设计索引
这样梳理,比单独记“什么是回表”“什么是最左匹配”更接近真正理解了数据库索引。
11. 最后总结
你可以把 MySQL 索引理解成三层:
- 结构层:B+ 树为什么适合数据库
- 存储层:聚簇索引、二级索引、回表、覆盖索引
- 工程层:联合索引顺序、失效场景、真实 SQL 优化
真正会用索引,不是背出定义,而是看到一个 SQL 时,脑子里能立刻出现这几个问题:
- 它会不会走索引
- 走的是哪个索引
- 会不会回表
- 扫描范围大不大
- 这个索引值不值得维护
当你开始用这种方式看 SQL,索引才算真正从“抽象概念”变成“工程工具”。