为什么MySQL索引用B+树?从存储底层讲透原理

大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。

"为什么MySQL的索引用B+树,不用哈希,不用红黑树?"很多DBA面试都会被问到这个问题。你的答案是"B+树矮胖、叶子节点有链表、支持范围查询"。面试官如果接着问:那为什么矮胖就好?你该怎么答?

我在这方面也被问住过,后来自己啃了几天存储引擎的源码和文档,才把这层窗户纸捅破。今天不背八股,从磁盘IO这个最底层的约束,一步步推,为什么最后是B+树。推完你会发现,后面那些索引设计的原则,全是这个原理长出来的。

一、一切的起点:磁盘IO很贵

数据库的瓶颈,绝大多数不在CPU,在磁盘IO。数据存在磁盘上,读一次要等磁头转过去。这个等待,比内存慢好几个数量级。

索引的价值,就是少读几次磁盘。你查一行数据,如果不走索引,可能要把整张表扫一遍,读几千个数据块。走索引,读几个块就定位到了。所以评价一个索引结构,核心指标就一个:一次查询,最少读几次磁盘。

这个"读几次磁盘",在磁盘术语里叫IO次数。数据库每次IO,读的是一页,不是一行。页是存储的最小单位,默认16KB。所以问题的本质变成:给定一个数据结构,查一条数据,要走多少次页。

二、为什么不用哈希

先看哈希。哈希索引查单点,确实快,一次哈希定位,接近O(1)。但它有个致命短板:只能查等值。

你写WHERE id = 5,哈希索引飞快。但写WHERE id > 5或者ORDER BY id,哈希直接废了。哈希是散列的,数据排不排序它不管,范围查询只能全表扫。

还有一个问题,哈希冲突。数据多了,冲突多了,性能就退化。所以哈希索引适合精确匹配,不适合当通用的主索引。

三、为什么不用二叉和红黑树

再看树。二叉搜索树,理论上查找是O(log n)。但它有个问题,树的高度和数据的插入顺序强相关。数据有序插入,二叉搜索树会退化成链表,高度变成n,查询变成O(n),等于全表扫。

红黑树解决了退化问题,它通过旋转保持平衡,高度稳定在log n。但注意,红黑树是"内存里的平衡树"。它的节点比较小,一个节点就一个数据。放到磁盘上,一个页16KB,如果只装一个数据,太浪费了。

更要命的是,红黑树高度对数据库来说还是太高。数据量一大,比如千万行,log2(1000万)大概是24。也就是说,查一次要走24层,24次磁盘IO。这太慢了。

四、B树:矮了,但还不够

B树就是来降低高度的。它的核心思想:一个节点多存几个数据,把树压矮。每个节点存一批键,扇出高,树就矮了。千万行数据,B树可能只要3到4层。

但B树有个特点:数据分散在所有的节点上,不光叶子节点。查一个数据,可能在根节点就命中了,也可能在中间的节点。这就带来一个问题:查询性能不稳定。同一张表,有的查询快,有的查询慢,看数据落在哪一层。

而且B树的叶子节点之间没有链表相连。要做范围查询,比如取id在100到200之间的所有数据,得从根节点开始,沿着树结构找到100所在的节点,再一层层回溯到父节点、找到200的位置,来回走好几趟。每走一层都是一次随机IO,效率远不如B+树顺着叶子节点链表一路扫过去。

五、B+树:为什么它赢了

B+树是B树的改良,改了两处,正好补上B树的短板。

第一处,数据全放在叶子节点。非叶子节点只存键,不存数据,用来当"目录"。这样每个非叶子节点能塞更多的键,扇出更高,树更矮。在MySQL默认16KB页大小和典型配置下,千万行的表,B+树通常3层就够了。3层,就是3次磁盘IO。

text 复制代码
第1层(根): [10, 20, 30]                 ← 只存键,当目录
第2层:      [1..10] [11..20] [21..30]      ← 还是目录
第3层(叶子): [数据][数据]...[数据][数据]   ← 真正的数据全在这
              ↑ 叶子之间用链表串起来

第二处,叶子节点用双向链表串起来。这直接解决了范围查询,从前向后扫一遍就行,不用在树里来回跳。要取id在100到200之间的数据,找到100所在的叶子,顺着链表往后扫就行。一次顺序IO,把整段数据读出来。顺序IO比随机IO快得多。

这两个改动,让B+树同时拿到了两个优势:树矮,IO次数少且稳定。支持范围查询,走顺序IO。所以它成了关系型数据库的主流索引结构。

六、这套原理,怎么指导日常设计

理解了底层,再回头看索引设计,很多"规则"就自动想通了。

先说覆盖索引。既然数据全在叶子节点,那如果查询要的列,叶子节点里都有,就不用回表。这就是覆盖索引。EXPLAIN里Extra显示Using index,就是命中覆盖索引。设计时,把高频查询要的列一起建进索引,能省一次回表IO。

sql 复制代码
-- 联合索引 (user_id, status),查询只要这两列 → 覆盖索引,不回表
EXPLAIN SELECT user_id, status FROM orders
WHERE user_id = 12345;
-- Extra: Using index

再说最左前缀。联合索引(a, b, c),数据按a排,a相同再按b排。所以查询必须从a开始,跳过a直接用b,索引就用不上。这不是规则,是B+树按序存储的必然结果。理解了结构,就不会再问"为什么最左前缀"。

还有前缀索引。大字段(比如长文本)建索引,整列建会很大,占空间。可以只取前N个字符建索引。但要注意,前缀索引不能做覆盖索引优化。这个取舍,也是从存储结构推出来的。

避坑清单

别给所有列都建索引。每个索引都是一棵B+树,写入时都要维护。索引越多,写入越慢,空间越大。我见过一张表建了十几个索引,写请求全堵在索引维护上。索引是给查询用的,写多的表,索引要克制。

联合索引别乱建,先想最左前缀。很多人把要用的列一股脑塞进联合索引,结果查询跳过第一列,索引白建。建之前,列一下实际查询的WHERE条件,按条件顺序建,比瞎建强。

别一听"覆盖索引好"就到处用。覆盖索引要包含所有查询列,列一多,索引就大,反而得不偿失。我的原则:只给高频查询、返回列少的场景用覆盖索引。低频的、SELECT *的,老实回表。

我的判断

B+树这套原理,看着像面试八股,其实每天都在用。你写的每条SQL,优化器都在背后做着"走哪棵B+树、读几次磁盘"的决策。懂原理的人,索引设计靠推导。不懂的人,只能靠猜和试。

这也是我为什么建议别背结论。哈希为什么不行,红黑树为什么不行,B+树为什么行,自己推一遍,比背十遍管用。

下次再有人问你"为什么用B+树",你不用背,从磁盘IO讲起就行。


你面试被B+树问倒过吗?索引设计翻过车没?评论区聊聊。我猜很多人背过"B+树矮胖"这句话,但没想过它到底为什么赢。

我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋

相关推荐
BestHeaker1 小时前
制造业 MES 开发入门:和互联网业务开发的 5 个本质差异(一)
数据库·经验分享·制造业·mes
自动化监测Learner2 小时前
Navicat Premium 17 中文版安装教程(2026最新版)
java·linux·数据库
张文是假的啊2 小时前
常用JPA注解解释
数据库
Allen_LVyingbo4 小时前
医疗AI基础2026-构建可靠智能体的编程路径(上)
大数据·数据库·人工智能·python·自动化
疯狂打码的少年4 小时前
【数据库技术】多值依赖与第四范式(4NF)
数据库·笔记·算法
微软技术分享4 小时前
Ubuntu 本地部署Ollama+OpenWebUI教程
数据库·ubuntu·postgresql
ly76894 小时前
MySQL 自增主键耗尽:从原理到应急恢复
数据库·mysql·innodb·故障恢复·自增主键·主键设计
文人sec5 小时前
MySQL主库出问题了,从库怎么办?备库为什么会延迟好几个小时?
android·数据库·mysql