MySQL索引底层最佳实践:B+树原理与索引设计

举报
数据库小学妹 发表于 2026/09/03 09:48:08 2026/09/03
【摘要】 大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。 “为什么MySQL的索引用B+树,不用哈希,不用红黑树?”很多DBA面试都会被问到这个问题。你的答案是"B+树矮胖、叶子节点有链表、支持范围查询"。面试官如果接着问:那为什么矮胖就好?你该怎么答? 我在这方面也被问住过,后来自己啃了几天存储引擎的源码和文档,才把这层窗户纸捅破。今天不背八股,从磁盘IO这个最底层的约束,一步步推,为什么最后是

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

“为什么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。

第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。

-- 联合索引 (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+树矮胖"这句话,但没想过它到底为什么赢。

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

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。