导读:本期聚焦于小伙伴创作的《MySQL索引底层到底是怎么实现的?B+树结构深度解读》,敬请观看详情。为什么MySQL不用哈希表或红黑树做默认索引,而选择了B+树?这得从磁盘IO特性说起。索引的本质是减少数据检索时的磁盘访问次数,B+树通过多路平衡设计让三层结构就能存下千万级数据。它的非叶子节点只存键值与指针,所有真实数据都落在叶子节点,并且叶子节点间用双向链表串联,范围查询时无需回到根节点反复查找。相比之下,红黑树高度太大,哈希表无法支持有序范围扫描。理解页分裂、聚簇索引与二级索引的回表机制,才能明白为什么模糊匹配前缀失效、为什么主键最好自增。下面从物理存储到查询路径完整拆解这套结构。

MySQL的索引并不是什么神秘组件,它本质是一套建立在磁盘页之上的有序数据结构。InnoDB存储引擎默认使用B+树来组织索引,之所以这样选,是因为数据库面对的是以磁盘为介质的持久化存储,每一次随机IO的代价都远高于内存计算。理解索引底层,要先抛开“索引就是加快查询”的笼统认知,去看它如何把查找过程中的磁盘访问次数压到最低。

MySQL索引底层到底是怎么实现的?B+树结构深度解读

一、为什么是B+树而不是其他结构

在众多数据结构中,哈希表、二叉搜索树、红黑树、B树都具备一定的检索能力,但放到数据库场景里各有短板。哈希表虽然等值查询是O(1),但它完全不支持范围查询,也无法利用“最左前缀”做排序,而业务里“查本月订单”这种范围语句无处不在。红黑树属于二叉树,每个节点最多两个分支,当数据量到千万级时树高会超过二十层,意味着一次查询可能要二十次磁盘IO,这显然不可接受。

B树虽然已经做了多路化,把多个键值塞进一个节点以减少高度,但它的非叶子节点也保存了完整行数据或数据指针,导致单个页能容纳的键值变少,树还是会偏高。B+树在此基础上做了关键改造:非叶子节点只保存键值和指向下层页面的指针,不存实际数据;所有真实记录都集中在叶子节点,并且叶子节点之间通过双向链表相连。这样非叶子节点能装下更多键,三层B+树就能索引上千万行,范围扫描只需顺着链表走。

二、InnoDB页与B+树的物理映射

InnoDB管理磁盘的最小单位是页(page),默认大小16KB。无论是表数据还是索引,都以页为单位读写。一个B+树节点就对应一个索引页,页内记录按照主键顺序排列,通过槽位(slot)做二分查找定位。当插入数据导致页满,就会触发页分裂,把一半记录移到新页,并在父节点增加指向新页的指针,以此维持平衡。

下面用伪代码描述一次简化的B+树节点分裂逻辑,帮助理解底层行为:

// 当一个索引页装满后触发分裂
public Page splitPage(Page fullPage) {
    Page newPage = allocateNewPage();
    // 把后半部分记录迁移到新页
    List<Record> records = fullPage.getRecords();
    int mid = records.size() / 2;
    for (int i = mid; i < records.size(); i++) {
        newPage.insert(records.get(i));
        fullPage.remove(records.get(i));
    }
    // 父节点记录新页的最小键与指针
    fullPage.getParent().insertKey(newPage.getMinKey(), newPage.getPointer());
    return newPage;
}

聚簇索引(clustered index)在InnoDB里就是主键B+树,叶子节点直接挂的是整行数据。如果表没显式定义主键,InnoDB会选第一个唯一非空索引,若还没有就隐式生成6字节行ID。二级索引的叶子节点存的是主键值,而不是行地址,所以通过二级索引查非索引列时,必须拿主键再去聚簇索引走一遍,这个过程叫回表。

三、查询路径与执行代价分析

假设执行语句 select * from user where id = 123,InnoDB会从聚簇索引的根页开始,依据每层节点里的键值范围决定下一步访问哪个子页,直到抵达叶子页找到记录。由于树高通常只有三到四层,一次主键查询只需三四次IO。若是二级索引查询且要回表,则先搜二级索引树拿到主键,再搜聚簇索引树,IO次数大约翻倍。

我们可以通过对比来看不同索引设计的差异:

结构类型范围查询单层扇出典型树高(千万数据)
红黑树不支持有序链扫220层以上
B树中序遍历较麻烦较低5到7层
B+树叶子链表顺扫3到4层

从表中能看出,B+树用更高的扇出压低了树高,又用叶子链表补齐了范围查询短板。这也是为什么MySQL把B+树作为唯一默认索引结构。

四、常见误区与优化建议

不少人在设计表时随意用UUID做主键,这会让聚簇索引的插入变成大量随机页写入,频繁触发页分裂,性能远不如自增主键的顺序追加。还有人以为 like '%abc' 也能走索引,实际上B+树的有序性只支持前缀匹配,左模糊和全模糊只能全表扫。

另一个坑是过度建二级索引。每个二级索引都是一棵独立B+树,写入时要同步维护,索引越多写放大越严重。建议只针对高频查询条件和联合索引的最左列建模,并用覆盖索引(查询列恰好都在索引里)避免回表。例如下面的联合索引就能让某些查询只走一次树:

-- 建立覆盖索引,避免回表
CREATE INDEX idx_name_age ON user (name, age);

-- 该查询只需扫描二级索引叶子节点
SELECT name, age FROM user WHERE name = '张三' AND age > 20;

搞清B+树的节点构成、页分裂以及聚簇和二级索引的差异,就能在表结构设计阶段避开大多数性能陷阱,也能在慢查询排查时准确判断是不是索引没用对。

MySQL索引B+树索引结构修改时间:2026-08-02 05:30:28

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。