文章目录
-
- [1. 引言](#1. 引言)
- [2. B+树基础结构](#2. B+树基础结构)
- [3. 为什么 MySQL 选择 B+树](#3. 为什么 MySQL 选择 B+树)
- [4. B+树索引的查找过程](#4. B+树索引的查找过程)
-
- [4.1 单值查找(Where id = 100)](#4.1 单值查找(Where id = 100))
- [4.2 范围查找(Where id between 100 and 200)](#4.2 范围查找(Where id between 100 and 200))
- [4.3 覆盖索引与回表](#4.3 覆盖索引与回表)
- [5. B+树索引的插入与页分裂](#5. B+树索引的插入与页分裂)
-
- [5.1 普通插入](#5.1 普通插入)
- [5.2 页分裂](#5.2 页分裂)
- [5.3 插入顺序优化建议](#5.3 插入顺序优化建议)
- [6. B+树索引的删除与页合并](#6. B+树索引的删除与页合并)
- [7. 聚簇索引与辅助索引](#7. 聚簇索引与辅助索引)
- [8. B+树索引的管理与优化建议](#8. B+树索引的管理与优化建议)
-
- [8.1 使用自增主键](#8.1 使用自增主键)
- [8.2 合理设计联合索引](#8.2 合理设计联合索引)
- [8.3 避免索引失效的场景](#8.3 避免索引失效的场景)
- [8.4 监控索引碎片](#8.4 监控索引碎片)
- [9. 总结](#9. 总结)
1. 引言
在上一篇《MySQL 学习的一大重点:索引(上)》中,我们聊了索引的基本概念、常见的哈希索引、全文索引以及聚簇索引与非聚簇索引的区别。本篇将深入 MySQL 默认存储引擎 InnoDB 中最核心的索引结构------B+树,彻底搞清楚为什么 MySQL 选择它、它是如何组织数据的,以及在实际应用中如何利用 B+树索引提升查询性能。
读完本文,你将能够:
- 画出一棵标准的 B+树结构,并说出每个节点里都存了什么
- 理解 B+树索引的单值查找、范围查找、排序过程
- 掌握 InnoDB 中聚簇索引与二级索引在 B+树上的存储差异
- 理解页分裂、页合并对性能的影响,并在工作中尽量避免
2. B+树基础结构
B+树是一种多路平衡搜索树,专为磁盘或其它直接存取辅助设备而设计。在 MySQL 中,每个索引都对应一棵 B+树,叶子节点存储完整的数据行(聚簇索引)或主键值(二级索引),非叶子节点只存储索引键和指向下一层节点的指针。
一棵典型的 B+树示意图如下:
#mermaid-svg-klAZnHHOWJ6pVDdd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-klAZnHHOWJ6pVDdd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-klAZnHHOWJ6pVDdd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-klAZnHHOWJ6pVDdd .error-icon{fill:#552222;}#mermaid-svg-klAZnHHOWJ6pVDdd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-klAZnHHOWJ6pVDdd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-klAZnHHOWJ6pVDdd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-klAZnHHOWJ6pVDdd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-klAZnHHOWJ6pVDdd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-klAZnHHOWJ6pVDdd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-klAZnHHOWJ6pVDdd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-klAZnHHOWJ6pVDdd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-klAZnHHOWJ6pVDdd .marker.cross{stroke:#333333;}#mermaid-svg-klAZnHHOWJ6pVDdd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-klAZnHHOWJ6pVDdd p{margin:0;}#mermaid-svg-klAZnHHOWJ6pVDdd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-klAZnHHOWJ6pVDdd .cluster-label text{fill:#333;}#mermaid-svg-klAZnHHOWJ6pVDdd .cluster-label span{color:#333;}#mermaid-svg-klAZnHHOWJ6pVDdd .cluster-label span p{background-color:transparent;}#mermaid-svg-klAZnHHOWJ6pVDdd .label text,#mermaid-svg-klAZnHHOWJ6pVDdd span{fill:#333;color:#333;}#mermaid-svg-klAZnHHOWJ6pVDdd .node rect,#mermaid-svg-klAZnHHOWJ6pVDdd .node circle,#mermaid-svg-klAZnHHOWJ6pVDdd .node ellipse,#mermaid-svg-klAZnHHOWJ6pVDdd .node polygon,#mermaid-svg-klAZnHHOWJ6pVDdd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-klAZnHHOWJ6pVDdd .rough-node .label text,#mermaid-svg-klAZnHHOWJ6pVDdd .node .label text,#mermaid-svg-klAZnHHOWJ6pVDdd .image-shape .label,#mermaid-svg-klAZnHHOWJ6pVDdd .icon-shape .label{text-anchor:middle;}#mermaid-svg-klAZnHHOWJ6pVDdd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-klAZnHHOWJ6pVDdd .rough-node .label,#mermaid-svg-klAZnHHOWJ6pVDdd .node .label,#mermaid-svg-klAZnHHOWJ6pVDdd .image-shape .label,#mermaid-svg-klAZnHHOWJ6pVDdd .icon-shape .label{text-align:center;}#mermaid-svg-klAZnHHOWJ6pVDdd .node.clickable{cursor:pointer;}#mermaid-svg-klAZnHHOWJ6pVDdd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-klAZnHHOWJ6pVDdd .arrowheadPath{fill:#333333;}#mermaid-svg-klAZnHHOWJ6pVDdd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-klAZnHHOWJ6pVDdd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-klAZnHHOWJ6pVDdd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-klAZnHHOWJ6pVDdd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-klAZnHHOWJ6pVDdd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-klAZnHHOWJ6pVDdd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-klAZnHHOWJ6pVDdd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-klAZnHHOWJ6pVDdd .cluster text{fill:#333;}#mermaid-svg-klAZnHHOWJ6pVDdd .cluster span{color:#333;}#mermaid-svg-klAZnHHOWJ6pVDdd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-klAZnHHOWJ6pVDdd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-klAZnHHOWJ6pVDdd rect.text{fill:none;stroke-width:0;}#mermaid-svg-klAZnHHOWJ6pVDdd .icon-shape,#mermaid-svg-klAZnHHOWJ6pVDdd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-klAZnHHOWJ6pVDdd .icon-shape p,#mermaid-svg-klAZnHHOWJ6pVDdd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-klAZnHHOWJ6pVDdd .icon-shape .label rect,#mermaid-svg-klAZnHHOWJ6pVDdd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-klAZnHHOWJ6pVDdd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-klAZnHHOWJ6pVDdd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-klAZnHHOWJ6pVDdd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 根节点(索引页)
内节点 1
内节点 2
叶子节点 1:键 10-20
叶子节点 2:键 21-30
叶子节点 3:键 31-40
叶子节点 4:键 41-50
数据页:存储真实行
数据页:存储真实行
数据页:存储真实行
数据页:存储真实行
- 根节点:存放主键范围与子节点指针,常驻内存。
- 内节点:结构与根节点相同,叶子节点的上一层,存储多个键值对,每个键值指向下层子节点。
- 叶子节点:存放完整索引键与对应的行记录(或主键值),所有叶子节点通过双向链表串联,从而支撑高效的范围查询。
- 非叶子节点不存数据:B+树最大的特点就是非叶子节点只存键和指针,不存整行数据。这样每个节点(通常是一个 16KB 的"页")可以容纳更多的键,降低树的高度,减少随机磁盘 I/O。
InnoDB 中,一个页的默认大小为 16KB。假设主键为 8 字节,指针为 6 字节,一个非叶子节点页大约可以存放 16*1024 / (8+6) ≈ 1170 个索引项。一棵高度为 3 的 B+树可以索引 1170*1170*16 ≈ 2000 万 行记录(叶子节点每条记录约 1KB),足见其索引能力之强。
3. 为什么 MySQL 选择 B+树
MySQL 选择 B+树作为默认索引结构,主要基于以下几点:
-
磁盘 I/O 友好
数据存储在磁盘上,查找数据的主要开销是磁盘随机读写。B+树每个节点可存储多个键值,一次磁盘 I/O 就能加载一整个节点(一个页),树高极低,通常 3~4 层即可索引千万级数据,磁盘 I/O 次数极少。以一个实际例子来说:在 2000 万行记录、每行约有 100 字节数据的情况下,B+树的查找路径通常只需要 2~3 次磁盘 I/O。如果换用二叉搜索树,树高度可能是 25~30 层,每次比较可能都需要一次随机 I/O,性能差距高达 10 倍以上。
-
高效的范围查询与排序
B+树的所有叶子节点构成有序双向链表,一旦找到第一个符合条件的叶子节点,即可沿着链表顺序扫描,无需像二叉搜索树那样不断回溯父节点。这对 SQL 中的
ORDER BY、GROUP BY、BETWEEN ... AND ...、>、<等范围查询极为友好。举个例子,当执行SELECT * FROM orders WHERE id BETWEEN 1000 AND 5000时,B+树只需在叶子节点链表里走"一条直线",而 B 树则需要在叶子节点和非叶子节点之间反复跳跃,因为 B 树非叶子节点也存数据,相同数据量的树高更高,路径更曲折。 -
优异的缓存命中率
非叶子节点只存键,占空间小,可以被数据库缓存池(Buffer Pool)高频缓存。根节点几乎永远活在内存中,高层节点也极容易被缓存命中,进一步减少磁盘 I/O。在高并发读场景下,缓存的命中率往往能达到 99% 以上,这对数据库的整体吞吐量有决定性影响。
-
与操作系统的预读机制契合
现代操作系统通常以"页"为单位预读相邻数据,B+树的叶子节点链表正好顺应了这种预读行为,顺序扫描时性能接近全表扫描(因为是顺序读),但只扫描真正需要的那部分数据,既保速度又省 I/O。
对比同类数据结构:
| 数据结构 | 等值查询 | 范围查询 | 树高 | 缓存友好 | 综合评价 |
|---|---|---|---|---|---|
| 哈希索引 | ⭐⭐⭐⭐⭐ 极快 | ❌ 不支持 | --- | ❌ 哈希冲突时退步 | 仅适合纯等值,场景受限 |
| 二叉搜索树 | ⭐⭐ 慢 | ⭐ 极慢 | 高(O(n)) | ❌ 树太高 | 不适合磁盘存储 |
| B 树 | ⭐⭐⭐ 良 | ⭐⭐ 一般 | 中 | ⭐⭐ 一般 | 非叶子也存数据,降低分支因子 |
| B+树 | ⭐⭐⭐⭐ 优 | ⭐⭐⭐⭐⭐ 优 | 低 | ⭐⭐⭐⭐ 优 | 综合最优,最适合数据库 |
相比之下,哈希索引虽然等值查询极快,但无法处理范围查询;二叉搜索树随着数据增长树高增加,磁盘 I/O 陡增;B 树虽然也在非叶子节点存储数据,但导致每个节点能存储的键更少,树高更高,且范围查询效率不如 B+树。B+树通过在非叶子节点不存数据、在叶子节点串成双向链表这两大设计,完美平衡了单值查询和范围查询的性能,堪称数据库存储引擎的最优解。
4. B+树索引的查找过程
4.1 单值查找(Where id = 100)
假设 id 为聚簇索引主键,查找过程如下:
- 从根节点(已缓存)开始,逐层比较主键值,找到对应的子节点页号;
- 不断向下一层加载索引页,直至叶子节点;
- 在叶子节点页内通过二分查找(页内记录按主键有序存放)找到
id=100的位置; - 如果该叶子节点还包含其他数据列,则直接返回整行。
整个过程磁盘 I/O 次数 = 树的高度(加载非叶子节点页) + 1(加载叶子节点页)。对于 2000 万行记录、树高为 3 的 B+树,只需 2~3 次磁盘 I/O。
4.2 范围查找(Where id between 100 and 200)
- 先通过单值查找定位到
id=100所在的叶子节点页; - 在该页内顺序扫描满足范围的记录;
- 若当前节点满足范围的数据已读完但仍未到 200,则通过叶子节点间的 next 指针跳转到下一个叶子节点页;
- 继续重复扫描,直到超出范围。
这样只需一次二叉查找定位 + 顺序读取几个相邻页,比全表扫描快得多。
4.3 覆盖索引与回表
当查询的列全部包含在索引中时(覆盖索引),直接通过索引叶子节点即可拿到所有数据,无需再根据主键回聚簇索引查取整行,极大减少磁盘 I/O。例如:
sql
SELECT name, age FROM user WHERE name = 'Alice';
如果 (name, age) 建有联合索引,则 name 键定位到叶子节点后,该节点已经包含了 age 值,直接返回,无需回表。
如果查询列未完全覆盖,比如 SELECT * FROM user WHERE name = 'Alice',则二级索引叶子节点只存 name 和 id主键,需要再根据 id 到聚簇索引中查找整行,称为回表。回表次数过多会显著影响性能,应用中应尽量设计覆盖索引。
5. B+树索引的插入与页分裂
5.1 普通插入
新行插入时,首先根据主键值找到应插入的叶子节点页。若该页有足够空闲空间(如剩余空间 > 1/16),则直接在页内合适位置插入记录并保持有序。若页面已满,则需要页分裂。
5.2 页分裂
页分裂是 B+树维护平衡和有序性的核心机制。但频繁的页分裂会导致磁盘 I/O 增加和索引碎片,进而影响性能。以自增主键为例:
如果主键是自增整数,所有新插入记录的主键总是大于已有最大值,因此总是插入到最右侧的叶子节点。当最右节点页满时,InnoDB 会分配一个新页,将原页内部分数据移动到新页,并在父节点插入指向新页的键,分裂成本相对较低。但如果使用随机主键(如 UUID),插入位置可能位于已有叶子节点中间,大量页分裂会发生在中间节点,每次分裂都要在父节点插入新键,极端情况下甚至会导致根节点分裂,性能剧烈抖动。
5.3 插入顺序优化建议
强烈建议 InnoDB 表使用自增、单调递增的主键(如 BIGINT AUTO_INCREMENT),避免随机主键引发的不必要页分裂。如果业务上一定要用 UUID 作为唯一标识,可以考虑将自增主键作为内部主键,UUID 仅作为业务列上的二级唯一索引,这样既能满足业务,又能保证插入性能。
6. B+树索引的删除与页合并
删除记录时,对应叶子节点页会标记该记录为删除(逻辑删除),并不会立即物理清空或合并页面。当页面内有效记录占比低于某个阈值(InnoDB 默认 MERGE_THRESHOLD=50%,即页内数据量小于页的一半),且相邻页也有足够空间时,InnoDB 会尝试将两个页面合并成一个页面,并相应更新父节点的指针,此举称为页合并。
页合并同样会带来额外的磁盘 I/O 和锁开销,但相比页分裂,其触发条件更宽松,通常在大量删除或更新操作后由后台线程异步完成,对业务影响相对较小。
值得注意的是,对于自增主键,如果大量删除旧数据,导致早期叶子节点变成"稀疏页",后台的页合并不但能回收空间,还能保持叶子节点链表的高效扫描能力。但若应用程序先大量删除,再大量插入(特别是随机主键),可能引发"分裂-合并"的恶性循环,严重拖垮性能。
7. 聚簇索引与辅助索引
InnoDB 中,表数据文件本身就是按 B+树组织的一个聚簇索引 。聚簇索引的叶子节点存储的是完整的行记录 。当我们创建一张 InnoDB 表而没有显式定义主键时,MySQL 会隐式寻找第一个 UNIQUE 且 NOT NULL 的列作为聚簇索引;若仍找不到,则会生成一个隐式的 6 字节 row_id 作为聚簇索引键。但强烈建议显式定义一个业务无关的自增主键。
所谓的二级索引 (或辅助索引)的叶子节点存储的是索引列的值加上对应的主键值。因此,通过二级索引查找需要两步:先在辅助索引 B+树中找到主键,再回到聚簇索引查找完整行(回表)。这也是为什么辅助索引的叶子节点不直接存数据指针的原因------数据位置可能因页分裂等操作而变动,存主键值更加稳定,且利于维护。
下图展示了聚簇索引与二级索引的关系:
#mermaid-svg-vbV3PLQjDodcIjst{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vbV3PLQjDodcIjst .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vbV3PLQjDodcIjst .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vbV3PLQjDodcIjst .error-icon{fill:#552222;}#mermaid-svg-vbV3PLQjDodcIjst .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vbV3PLQjDodcIjst .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vbV3PLQjDodcIjst .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vbV3PLQjDodcIjst .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vbV3PLQjDodcIjst .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vbV3PLQjDodcIjst .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vbV3PLQjDodcIjst .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vbV3PLQjDodcIjst .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vbV3PLQjDodcIjst .marker.cross{stroke:#333333;}#mermaid-svg-vbV3PLQjDodcIjst svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vbV3PLQjDodcIjst p{margin:0;}#mermaid-svg-vbV3PLQjDodcIjst .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vbV3PLQjDodcIjst .cluster-label text{fill:#333;}#mermaid-svg-vbV3PLQjDodcIjst .cluster-label span{color:#333;}#mermaid-svg-vbV3PLQjDodcIjst .cluster-label span p{background-color:transparent;}#mermaid-svg-vbV3PLQjDodcIjst .label text,#mermaid-svg-vbV3PLQjDodcIjst span{fill:#333;color:#333;}#mermaid-svg-vbV3PLQjDodcIjst .node rect,#mermaid-svg-vbV3PLQjDodcIjst .node circle,#mermaid-svg-vbV3PLQjDodcIjst .node ellipse,#mermaid-svg-vbV3PLQjDodcIjst .node polygon,#mermaid-svg-vbV3PLQjDodcIjst .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vbV3PLQjDodcIjst .rough-node .label text,#mermaid-svg-vbV3PLQjDodcIjst .node .label text,#mermaid-svg-vbV3PLQjDodcIjst .image-shape .label,#mermaid-svg-vbV3PLQjDodcIjst .icon-shape .label{text-anchor:middle;}#mermaid-svg-vbV3PLQjDodcIjst .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vbV3PLQjDodcIjst .rough-node .label,#mermaid-svg-vbV3PLQjDodcIjst .node .label,#mermaid-svg-vbV3PLQjDodcIjst .image-shape .label,#mermaid-svg-vbV3PLQjDodcIjst .icon-shape .label{text-align:center;}#mermaid-svg-vbV3PLQjDodcIjst .node.clickable{cursor:pointer;}#mermaid-svg-vbV3PLQjDodcIjst .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vbV3PLQjDodcIjst .arrowheadPath{fill:#333333;}#mermaid-svg-vbV3PLQjDodcIjst .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vbV3PLQjDodcIjst .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vbV3PLQjDodcIjst .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vbV3PLQjDodcIjst .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vbV3PLQjDodcIjst .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vbV3PLQjDodcIjst .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vbV3PLQjDodcIjst .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vbV3PLQjDodcIjst .cluster text{fill:#333;}#mermaid-svg-vbV3PLQjDodcIjst .cluster span{color:#333;}#mermaid-svg-vbV3PLQjDodcIjst div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vbV3PLQjDodcIjst .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vbV3PLQjDodcIjst rect.text{fill:none;stroke-width:0;}#mermaid-svg-vbV3PLQjDodcIjst .icon-shape,#mermaid-svg-vbV3PLQjDodcIjst .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vbV3PLQjDodcIjst .icon-shape p,#mermaid-svg-vbV3PLQjDodcIjst .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vbV3PLQjDodcIjst .icon-shape .label rect,#mermaid-svg-vbV3PLQjDodcIjst .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vbV3PLQjDodcIjst .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vbV3PLQjDodcIjst .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vbV3PLQjDodcIjst :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 二级索引 B+树(name)
叶子节点:name + id
聚簇索引 B+树(id)
叶子节点:完整行记录
理解了这种架构,我们才能进一步理解联合索引、覆盖索引、索引下推等高级优化手段。
8. B+树索引的管理与优化建议
8.1 使用自增主键
如上所述,自增主键可以保证插入顺序,减少页分裂,提升写入性能。同时,二级索引叶子节点存的是主键值,如果主键较长(如长 UUID 字符串),所有二级索引都会变得臃肿,既占空间,又降低缓存效率。因此,主键应尽量短小单调递增。
8.2 合理设计联合索引
联合索引遵循最左前缀原则 :只有当查询条件包含联合索引的左侧列时,该索引才能被利用。例如索引 (a, b, c) 可以支持 WHERE a = 1、WHERE a = 1 AND b = 2,但不支持 WHERE b = 2。因此,要把区分度高、经常作为查询条件的列放在联合索引的最左边。
8.3 避免索引失效的场景
- 在索引列上使用函数或运算:
WHERE YEAR(create_time) = 2026无法使用索引,应改写为范围查询。 - 隐式类型转换:如
varchar列与数值比较,可能导致全表扫描。 - 模糊查询以
%开头:LIKE '%abc'无法利用 B+树的排序特性。 - 使用
!=、<>、NOT IN等可能导致优化器选择全表扫描。
8.4 监控索引碎片
系统运行一段时间后,B+树叶子节点可能会产生碎片(页面填充率不均匀),可以通过 OPTIMIZE TABLE 或重建表来整理碎片,恢复插入和扫描性能。但在线业务执行重建操作需谨慎,可能会锁表或造成主从延迟,建议在业务低峰期操作。
9. 总结
B+树是 MySQL 甚至整个关系型数据库世界的"大脑",它用极低的树高和高效的叶子节点链表,同时支撑了高性能的单值查找和范围查询。我们从 B+树的结构、查找、插入删除机制切入,进一步理解了聚簇索引与二级索引的底层存储差异,以及页分裂、页合并对写入性能的深远影响。
在实际工作中,牢记以下几点:
- 主键尽量自增,不要随意使用 UUID
- 利用覆盖索引避免回表,减少磁盘 I/O
- 最左前缀原则是联合索引的生命线
- 避免在索引列上使用函数或隐式类型转换
掌握这些原理后,当你再次面对慢查询、索引设计时,就能从 B+树的角度出发,做出高效的优化决策。下篇预告:MySQL 索引调优实战,结合执行计划彻底攻克慢 SQL。