MySQL 8 索引与 B+ 树-初浅理解

一、为什么要理解索引结构?

我们平常写下的 SQL:

SELECT * FROM user WHERE id = 100;

虽然只是一行查询语句,但 MySQL 背后是如何“知道”该去哪一页找数据的?
为什么即使有上千万行,也能瞬间查到?

核心答案只有三个字:B+ 树


二、索引的本质是什么?

在 InnoDB 引擎中:

  • 索引(Index)不是“附加文件”,而是数据的有序组织结构
  • MySQL 通过 B+ 树(Balanced Plus Tree) 实现高效查找;
  • 所有表数据都存储在主键 B+ 树的叶子节点中。

一张表,其实就是一棵以主键为索引的 B+ 树。


三、建表示例

CREATE TABLE A (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50),
  age INT
) ENGINE=InnoDB;
  • id 是主键;
  • MySQL 自动为它建立聚簇索引(Clustered Index)。

四、插入数据:B+ 树的起点

INSERT INTO A (name, age) VALUES ('Tom', 18);
INSERT INTO A (name, age) VALUES ('Jerry', 22);

表中数据:

idnameage
1Tom18
2Jerry22

初始结构(单页)

┌────────────────────────────────┐
│  Root/Leaf Page(同一个页)     │
│--------------------------------│
│ id=1 | name=Tom | age=18        │
│ id=2 | name=Jerry | age=22      │
└────────────────────────────────┘

只有一个页(Page),所有数据都在这个叶子页中。


五、页分裂:B+ 树如何“长大”

InnoDB 的每个数据页大小为 16KB
一行约 60 字节,一个页能存 约 270 行

当插入第 271 行时,页满了,触发 页分裂(Page Split)


分裂前

Page#1
┌─────────────────────────────┐
│ id=1 ~ id=270               │
│ (已满)                      │
└─────────────────────────────┘

分裂后

           Root Page
           ┌───────────────┐
           │ key = 135     │
           └───────────────┘
              /         \
             /           \
┌────────────────┐   ┌────────────────┐
│ Page#1: id 1~135 │   │ Page#2: id 136~270 │
└────────────────┘   └────────────────┘

此时树高 = 2 层(Root + Leaf)。


六、数据继续增长:形成三层结构

当页越来越多时,Root 页也可能装不下所有指针,
此时会再次分裂,形成 3 层结构。

                     Root
                  (Level 3)
                     │
     ┌───────────────┴───────────────┐
     ▼                               ▼
 Internal Page#10               Internal Page#20
     │                               │
     ▼                               ▼
┌────────────┐                 ┌────────────┐
│Leaf Page#1 │──▶ ... ──▶│Leaf Page#N │
└────────────┘                 └────────────┘
  • Root:保存中间节点指针
  • Internal Node:保存页号范围
  • Leaf Node:保存完整行数据
  • 所有 Leaf 之间用双向链表连接,便于范围查询。

七、普通索引(二级索引)结构

再创建一个索引:

CREATE INDEX idx_name ON A(name);

生成一棵独立的 B+ 树(存储 name 和对应主键 id):

         idx_name(二级索引)
                 Root
                  │
       ┌──────────┴──────────┐
       ▼                     ▼
   name=Jerry,id=2      name=Tom,id=1

二级索引的叶子节点不存整行数据,只存“索引列 + 主键 id”。
当用 name 查找时,需要回表


回表示意

SELECT * FROM A WHERE name = 'Tom';

执行过程:

  1. 在 idx_name 树查 name='Tom' → 得到 id=1
  2. 去主键树查 id=1 → 获取完整行
idx_name树: name='Tom' → id=1
         ↓
主键树: id=1 → (Tom, 18)

八、B+ 树查找过程

以三层结构为例:

           [Root]
             │
         ┌───┴───┐
         ▼       ▼
    [Internal] [Internal]
         │
         ▼
      [Leaf Pages]

查找 id=900000 时:

  1. 从 Root 判断区间;
  2. 进入对应的 Internal Page;
  3. 定位到 Leaf Page;
  4. 顺序扫描到匹配记录。

全程仅需 3 次磁盘 I/O(树高 3)。


九、页分裂与性能影响

插入模式分裂频率碎片性能
自增主键极少✅ 最优
随机 UUID频繁❌ 慢
批量插入可控👍 稳定

建议使用自增主键,保证插入顺序、减少分裂与碎片。


十、百万级数据的树高度

层级每节点页数可容纳行数
叶子页270 行270
第二层1000 页27 万
第三层1000 页2.7 亿

百万级数据时,树高通常只有 3 层。
查找只需 3 次磁盘 I/O。


十一、整体结构回顾图

                   [Root]
                     │
    ┌────────────────┴────────────────┐
    ▼                                 ▼
[Internal#1]                    [Internal#2]
    │                                 │
    ▼                                 ▼
[Leaf#1] ⇄ [Leaf#2] ⇄ ... ⇄ [Leaf#N]
  • Root:索引入口
  • Internal:保存页指针
  • Leaf:存放整行数据
  • 叶子之间用双向链表连接(便于范围扫描)

十二、总结

概念说明
聚簇索引表数据即索引叶子
二级索引需通过主键回表
页分裂数据页满后自动拆页
B+ 树高度通常 3~4 层即可支持亿级数据
自增主键能保持索引有序、减少分裂

十三、一句话总结

在 MySQL 8 中,索引不是“附加文件”,
而是数据本身的有序存储结构。
理解 B+ 树结构,就理解了 MySQL 的性能之魂。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值