欢迎来到窥视未来的博客

https://github.com/lwx57280 https://gitee.com/li_VillageHead

MySQL索引底层原理:从二叉树到B+树,一文读懂索引的“进化史”

索引是MySQL性能优化的核心,但你知道索引在数据库内部到底是什么样的数据结构吗?为什么MySQL选择B+树而不是二叉树或B树?InnoDB和MyISAM的索引实现有何不同?本文从最基础的查找算法讲起,层层递进,图文并茂地揭开索引的神秘面纱。

一、索引的本质:一个“目录”而已

索引的定义:数据库管理系统中一种排序的数据结构,用以快速查询、更新数据库表中的数据。

可以类比字典的目录:没有目录,你要找一个字可能需要逐页翻阅(全表扫描);有了目录,直接定位到页码,大大减少查找时间。

在数据库中,索引同样是为了减少磁盘I/O次数。内存的读写速度是纳秒级,磁盘是毫秒级,两者相差数万倍。因此,如何用最少的磁盘访问找到数据,是索引设计的核心目标。

二、索引数据结构的演进

1. 顺序查找 —— O(N)

最简单粗暴:从头到尾遍历。数据量一大,效率极低。

2. 二叉树 —— 二分查找思想

每个节点最多两个子节点。如果数据有序插入,二叉树会退化成链表,查找变成O(N)。

image

 

 

退化的二叉树,查询4需要4次比较。

3. 平衡二叉树(AVL / 红黑树)

通过旋转保证左右子树高度差不超过1,查找效率稳定在O(log N)。但每个节点只存储一个键值,树的高度依然较高。对于千万级数据,树高可能达到20~30层,意味着每次查询需要20~30次磁盘I/O,无法接受。

4. B树(多路平衡查找树)

B树将多个键值和数据存储在一个节点中,每个节点可以有多个子节点(多路)。一个节点就是一个磁盘页(16KB),能容纳更多键值,从而大大降低树的高度。

B树节点结构:每个节点包含多个键值 + 指向子节点的指针 + 数据(或数据指针)。

5. B+树 —— MySQL的最终选择

B+树是B树的变种,核心改进:

  • 非叶子节点只存储索引键值和子节点指针,不存储数据。因此每个节点能存储的索引项更多,树更矮胖。

  • 所有数据行都存储在叶子节点,且叶子节点之间通过双向链表串联,便于范围扫描。

B+树计算示例(InnoDB默认页16KB):

  • 假设索引键为INT(4字节)+ 指针(6字节)= 10字节,这里按14字节粗略估算。每个非叶子节点可存储:16KB / 14 ≈ 1170 个索引项。

  • 每个叶子节点可存约16条数据记录(假设一行约1KB)。

  • 那么一个3层的B+树(根节点+第二层+叶子层)可存储的数据行数为:
    1170 × 1170 × 16 ≈ 2190万 条记录。

 

image

 

三层树只需要3次磁盘I/O,就能在两千万数据中定位到目标行!性能远超平衡二叉树。

三、B-树 vs B+树 对比图

 

image

 

 左图B树:  非叶子节点也带data;右图B+树:非叶子节点只有键,叶子节点存储data并用链表相连。

B+树的优势总结

  • 扫库、扫表能力更强:只需扫描叶子节点链表。

  • 磁盘读写能力更强:非叶子节点不存数据,单页存更多索引,树高更低。

  • 排序能力更强:叶子节点链表天然有序。

  • 效率更稳定:所有查询都要到叶子节点,I/O次数恒定。

四、InnoDB vs MyISAM:索引实现大不同

两款引擎都使用B+Tree,但组织和存储方式截然不同。

MyISAM(非聚簇索引)

  • 文件:.MYD(数据文件)+ .MYI(索引文件),索引与数据分离。

  • 索引叶子节点:存储数据行的磁盘指针(物理地址)。

  • 查找过程:在.MYI中找到键对应的指针 → 根据指针到.MYD中读取数据行。

image

 

InnoDB(聚簇索引)

  • 文件:.ibd文件同时存储数据和索引。

  • 主键索引(聚簇):叶子节点直接存储完整行记录。数据按照主键顺序物理存放。

  • 辅助索引(二级):叶子节点存储主键值,查询需要先查二级索引获得主键,再回到主键索引中查找完整行——这叫回表(Bookmark Lookup)。

image

 

 

如果没有主键,InnoDB会选择一个唯一非空索引作为聚簇索引;否则隐式生成6字节的ROW_ID。

为什么InnoDB推荐自增整型主键?

  • 整型比较快,占空间小,树高更低。

  • 自增保证新数据总是在B+树最右端插入,避免页分裂。而UUID等随机值会导致频繁的页分裂、数据移动和磁盘碎片,严重影响写入性能。

五、回表、覆盖索引与索引下推

1. 回表

通过二级索引查询时,需要二次查找主键索引才能拿到完整数据,这个过程叫回表。回表增加了一次B+树查找,应尽量避免。

2. 覆盖索引

如果查询的所有字段都包含在某个二级索引中,那么只需扫描该索引即可返回结果,无需回表。EXPLAIN中Extra列显示Using index。

-- 覆盖索引查询,不回表
EXPLAIN SELECT name, phone FROM user WHERE name='张三';

-- 需要回表
EXPLAIN SELECT * FROM user WHERE name='张三';

 

3. 索引下推(ICP, Index Condition Pushdown)

MySQL 5.6引入,将WHERE条件中能使用索引过滤的部分下推到存储引擎层,在索引层面提前过滤,减少回表次数。

示例:SELECT * FROM user WHERE name LIKE '张%' AND age=18,联合索引(name, age)。

  • 无ICP:存储引擎先找出所有name以“张”开头的记录的主键,回表读取完整行,再在Server层判断age=18。

  • 有ICP:存储引擎在索引层同时使用name和age过滤,只有满足age=18的记录才回表。

EXPLAIN的Extra列显示Using index condition。

 

六、索引失效的典型场景

 
失效场景示例原因
索引列使用函数 WHERE YEAR(create_time)=2024 无法使用索引,应改写为范围查询
隐式类型转换 WHERE phone=13800000000(phone是VARCHAR) 类型转换导致索引失效
LIKE以通配符开头 WHERE name LIKE '%张' 无法利用B+树前缀查找
OR条件非全索引 WHERE a=1 OR b=2,b无索引 优化器放弃索引
负向查询 WHERE status != 0、NOT IN 通常不走索引

 

七、索引设计建议

  1. 在WHERE、JOIN、ORDER BY、GROUP BY字段上建索引。

  2. 索引个数不要过多,影响写入性能。

  3. 高离散度列优先:离散度公式 COUNT(DISTINCT col)/COUNT(*),越接近1越好。性别列不宜建索引。

  4. 避免频繁更新的列做主键或索引,会引发页分裂。

  5. 组合索引将区分度高的列放在最左边(最左前缀原则)。

  6. 使用覆盖索引,只查询必要字段,避免SELECT *。

  7. 过长的字符串字段使用前缀索引,如CREATE INDEX idx_name ON user(name(10))。

八、总结

  • 索引本质:排序的数据结构,核心目的是减少磁盘I/O。

  • B+Tree的优势:非叶子节点只存索引,树高很低;叶子节点链表支持高效范围查询;查询效率稳定。

  • InnoDB采用聚簇索引:主键即数据,辅助索引需回表;MyISAM索引与数据分离。

  • 覆盖索引和索引下推是MySQL的重要优化手段,能大幅减少回表。

  • 索引设计需要权衡查询效率与写入成本,并非越多越好。

理解索引的底层原理,不仅能帮你写出高效的SQL,更能让你在面对慢查询时快速找到症结。希望本文的图解和案例能让你对MySQL索引有一个立体的认知。

如果觉得本文对你有帮助,欢迎点赞、收藏、转发!
关注我,获取更多数据库底层原理与性能优化干货。

posted on 2026-05-03 20:13  k8s-Mango  阅读(119)  评论(0)    收藏  举报

导航