news 2026/7/17 6:35:58

MySQL 索引数据结构:一棵树如何拯救你的查询速度

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL 索引数据结构:一棵树如何拯救你的查询速度

"SELECT * FROM users WHERE name = '张三' 用了 0.5 秒?你该了解一下索引了。"


一、开场白:为什么你的查询比蜗牛还慢?

想象一下这个场景:你在一个有一亿条记录的表里查一条数据,没有索引。MySQL 的表情大概是这样的:

"好的,我从第一行开始看……第二行……第三行……第 87,432,651 行……找到了!"

这就是传说中的全表扫描(Full Table Scan)——像在没有目录的字典里找一个字,一页一页翻。效率感人,老板流泪。

那么索引是什么?索引就是书的目录。想翻到第 5 章?不用从第 1 页开始翻,直接看目录,跳过去,搞定。

但问题来了:MySQL 的索引用的是什么数据结构?为什么不用二叉树?为什么不用 Hash?为什么偏偏是B+ 树

坐好,我们从头讲起。


二、先认识几个"候选人"

在 MySQL 选择索引数据结构的路上,有好几个候选人来面试。让我们看看它们各自的表现。

2.1 二叉搜索树(BST):第一个被淘汰的选手

二叉搜索树的逻辑很简单:左子树所有值 < 根节点 < 右子树所有值。查找时间复杂度是 O(log n),看起来不错对吧?

但问题在于:它太"瘦高"了。

一棵存了 100 万条记录的二叉树,高度大约是 20 层。每读一个节点就是一次磁盘 I/O(后面会详细讲),20 次磁盘 I/O……你的查询还没返回,咖啡都凉了。

更可怕的是,如果插入的数据是有序的(比如自增主键),二叉搜索树会退化成一条链表,查找变成 O(n)——比全表扫描好不了多少。

二叉搜索树面试时说:"我能 O(log n)!" DBA 问:"你有多少层?" "20 层。" "下一位。"

2.2 红黑树 / AVL 树:矮是矮了,但还是太"瘦"

红黑树和 AVL 树是自平衡的二叉树,保证了树的高度不会太高。Java 的TreeMap、C++ 的std::map底层就是它们。

但问题是:它们每个节点只有两个子节点(二叉嘛)。即使平衡了,100 万条数据仍然需要大约 20 层。

20 层 = 20 次磁盘 I/O = 20 次"去磁盘上找一下" = 20 次灵魂拷问。

红黑树:"我有颜色!我自平衡!" DBA:"你有几个孩子?" "两个。" "太少了。下一位。"

2.3 Hash 表:速度飞快,但"偏科生"

Hash 表的查找速度是O(1)——常数时间!不管你有多少数据,一次哈希计算直接定位。听起来完美?

但 Hash 表是个严重的"偏科生":

  • 不支持范围查询WHERE age > 18?Hash 表一脸懵逼:"我只知道等于,大于是什么?"

  • 不支持排序ORDER BY name?Hash 表:"顺序是什么?能吃吗?"

  • 不支持最左前缀匹配:联合索引?Hash 表:"我只认完整的 key。"

  • 存在哈希冲突:数据量大了以后,不同的 key 可能映射到同一个位置,需要遍历链表。

MySQL 中只有Memory 存储引擎默认使用 Hash 索引,InnoDB 有一个自适应哈希索引(Adaptive Hash Index),但那是自动的,你没法手动控制。

Hash 表面试时说:"我 O(1)!" DBA:"范围查询呢?" "不会。" "排序呢?" "不会。" "那你来干嘛?" "……我快。" "快有什么用,功能不全啊。下一位。"

2.4 B 树(B-Tree):优秀,但可以更好

B 树是一种多路平衡查找树,每个节点可以有多个子节点(不只是两个)。这意味着同样数据量下,B 树比二叉树矮得多

B 树的特点:

  • 每个节点既存索引 key也存数据 data

  • 所有叶子节点都在同一层

  • 一个节点可以有多个子节点(比如 100 个)

100 万条数据,B 树可能只需要3-4 层。3-4 次磁盘 I/O 就能定位数据,比二叉树的 20 次好太多了!

但 B 树有一个问题:每个节点都存了数据 data,导致每个节点能存的 key 数量变少了,树就相对变高了。

而且 B 树的叶子节点之间没有指针相连,范围查询需要中序遍历整棵树,效率不够好。

B 树面试时说:"我矮!我多路!我平衡!" DBA:"不错。但你每个节点都塞数据,太胖了。有没有更精简的方案?" B 树看了一眼旁边的弟弟:"……我弟可以。"


三、B+ 树:最终赢家登场!

B+ 树是 B 树的改良版,也是 MySQL InnoDB 存储引擎的默认索引结构。如果说 B 树是一个人既当厨师又当服务员,那 B+ 树就是专人专事——非叶子节点只负责索引,叶子节点才存数据。

3.1 B+ 树 vs B 树:核心区别

特性B 树B+ 树
非叶子节点存数据✅ 存❌ 不存,只存 key
叶子节点链表❌ 没有✅ 有双向链表
范围查询需要中序遍历沿链表顺序扫描即可
查询稳定性不稳定(可能在任意层找到)稳定(一定走到叶子节点)
每个节点能存的 key 数较少(因为存了 data)较多(只存 key)

3.2 为什么 B+ 树这么"矮胖"?

这是 B+ 树最大的优势:矮胖

来算一笔账。InnoDB 的一个页(Page)大小是16KB。假设:

  • 一个索引 key 是 8 字节(比如 bigint)

  • 一个指针是 6 字节

  • 那么一个页大约能存:16384 / (8 + 6) ≈ 1170个 key

两层 B+ 树:根节点 1170 个 key,每个指向一个叶子节点,每个叶子节点假设存 100 条数据: → 可以存储1170 × 100 = 117,000条数据

三层 B+ 树1170 × 1170 × 100 ≈ 1.37 亿条数据

也就是说:1.37 亿条数据,只需要 3 次磁盘 I/O 就能定位!

B+ 树:"我 3 层楼就能装 1.37 亿条数据。" 红黑树:"我需要 20 多层。" B+ 树:"哦。" (凡尔赛本赛)

3.3 叶子节点的链表:范围查询的神器

B+ 树的叶子节点之间通过双向链表连接。这意味着:

  • 范围查询WHERE age BETWEEN 18 AND 30,先定位到 18,然后沿着链表往后扫到 30,完事。

  • ORDER BY:链表本身就是有序的,排序零成本。

  • 全表扫描:只需要遍历叶子节点链表,不需要遍历整棵树。

这就是为什么 B+ 树在数据库场景下吊打其他数据结构的原因。


四、InnoDB 中的索引:聚簇索引 vs 二级索引

讲完了数据结构,我们来看 MySQL InnoDB 中索引的具体实现。这里有两个核心概念:聚簇索引(Clustered Index)二级索引(Secondary Index)

4.1 聚簇索引:数据本身就是索引

InnoDB 的聚簇索引有一个非常特别的设计:叶子节点存储的是完整的行数据

也就是说,聚簇索引的叶子节点 = 数据页。数据和主键索引是一体的,表数据本身就是一棵 B+ 树

这就像一本字典,目录和正文是合在一起的——找到目录项,就直接看到了释义。

InnoDB 选择主键作为聚簇索引的 key。如果没有定义主键,InnoDB 会选一个唯一的非空索引;如果还没有,InnoDB 会自动生成一个隐藏的 6 字节 ROW_ID 作为主键。

4.2 二级索引:回表的代价

除了主键索引,你创建的其他索引(比如INDEX idx_name (name))都是二级索引

二级索引的叶子节点存储的不是完整行数据,而是主键值

查找流程:

  1. 在二级索引的 B+ 树中找到name = '张三',得到主键值id = 42

  2. 拿着id = 42去聚簇索引(主键索引)的 B+ 树中再查一次,得到完整行数据

这个过程叫回表(Back to Table)——就像你先在作者索引里找到了书名,再去书架上找那本书。

二级索引:"我找到了张三的 id 是 42。" DBA:"然后呢?" 二级索引:"然后你自己去主键索引里找完整数据吧。" DBA:"……你就不能一次性给我?" 二级索引:"不能,我只存了主键。告辞。"

4.3 覆盖索引:避免回表的优化技巧

如果你查询的字段刚好都在二级索引里,就不需要回表了。这就是覆盖索引(Covering Index)

-- 假设有索引 INDEX idx_name_age (name, age) ​ -- 需要回表(SELECT * 需要所有字段) SELECT * FROM users WHERE name = '张三'; ​ -- 不需要回表!name 和 age 都在索引里 SELECT name, age FROM users WHERE name = '张三';

EXPLAIN中,如果看到Using index,说明用到了覆盖索引,没有回表。

覆盖索引:"你要的字段我都有,别回表了。" DBA:"你真是个好索引。" 覆盖索引:"那当然。我可是精心设计的。"


五、联合索引与最左前缀原则

5.1 联合索引的 B+ 树长什么样?

假设你有一个联合索引INDEX idx_abc (a, b, c),B+ 树的排序规则是:

先按 a 排序,a 相同的按 b 排序,b 相同的按 c 排序。

就像字典先按拼音排序,拼音相同按声调排序,声调相同按笔画排序。

5.2 最左前缀原则

这是面试高频考点,也是日常开发最容易踩坑的地方。

-- 有索引 INDEX idx_abc (a, b, c) ​ -- ✅ 能用到索引 WHERE a = 1 WHERE a = 1 AND b = 2 WHERE a = 1 AND b = 2 AND c = 3 ​ -- ✅ 能用到部分索引(只用到 a) WHERE a = 1 AND c = 3 ​ -- ❌ 用不到索引 WHERE b = 2 WHERE b = 2 AND c = 3 WHERE c = 3

为什么?因为 B+ 树是按(a, b, c)的顺序排序的。如果你不给 a,直接查 b,就像字典里没有拼音直接找声调——完全无从下手。

最左前缀原则:"你得先告诉我 a 是什么,我才能帮你找 b。" 开发者:"那我直接查 b 呢?" 最左前缀原则:"那你自己全表扫描吧,我帮不了你。" 开发者:"……"

5.3 索引下推(Index Condition Pushdown, ICP)

MySQL 5.6 引入了索引下推(ICP)优化。对于联合索引(a, b, c)

WHERE a = 1 AND c = 3

在没有 ICP 的时候:MySQL 在索引中找到a = 1的所有记录,然后全部回表,再在 server 层过滤c = 3

有了 ICP 之后:MySQL 在索引中找到a = 1的记录后,直接在存储引擎层检查c = 3,不满足条件的就不回表了。减少了回表次数。

ICP:"虽然你没按最左前缀查 c,但我在索引里帮你先过滤一波,少回几次表。" DBA:"你真是个贴心的优化器。"


六、磁盘 I/O:为什么"少读几次"这么重要?

6.1 磁盘 vs 内存:速度差了 10 万倍

这里要科普一个关键知识点:磁盘 I/O 是数据库最大的性能瓶颈。

存储类型访问时间
L1 缓存~1ns
L2 缓存~4ns
内存(RAM)~100ns
SSD~100μs(100,000ns)
HDD 机械硬盘~10ms(10,000,000ns)

HDD 比内存慢10 万倍。每一次磁盘读取,CPU 都要等很久。

这就是为什么 B+ 树的"矮胖"特性如此重要——树矮一层,就少一次磁盘 I/O。从 20 层降到 3 层,意味着从 20 次磁盘 I/O 降到 3 次,查询速度提升了一个数量级。

6.2 局部性原理与预读

操作系统和磁盘有一个优化机制叫局部性原理(Principle of Locality)

  • 时间局部性:最近访问的数据,很可能马上又会被访问

  • 空间局部性:访问了某个地址附近的数据,很可能也会被访问

基于这个原理,操作系统在读取磁盘时不会只读一个字节,而是预读(Read-ahead)一整个页(通常 4KB)。

InnoDB 正是利用了这一点:它的页大小是16KB(默认),每次读取一个完整的页。B+ 树的一个节点就是一个页,所以读一个节点 = 一次磁盘 I/O = 读 16KB 数据

操作系统:"你要读一个字节?我给你读 4KB,反正你大概率会用到旁边的数据。" InnoDB:"那我要 16KB。" 操作系统:"你是有多贪?" InnoDB:"我一个节点就这么大。"


七、实战:索引设计的"军规"

理论讲完了,来点实战建议。

7.1 选择性(Cardinality)是关键

选择性 = 不重复的索引值 / 数据总记录数

选择性越高,索引效果越好。比如:

  • gender(性别)字段只有 M/F 两个值,选择性 ≈ 2/N ≈ 0,建索引基本没用

  • email字段几乎每个值都不同,选择性 ≈ 1,非常适合建索引

-- 查看索引选择性 SHOW INDEX FROM your_table; -- 看 Cardinality 列

7.2 覆盖索引优先

尽量让查询的字段都在索引里,避免回表:

-- 差:SELECT * 无法覆盖索引 SELECT * FROM orders WHERE user_id = 100; ​ -- 好:只查需要的字段,且都在索引里 SELECT order_id, amount FROM orders WHERE user_id = 100;

7.3 不要在索引列上做函数操作

-- ❌ 索引失效! SELECT * FROM users WHERE YEAR(create_time) = 2024; ​ -- ✅ 改成范围查询 SELECT * FROM users WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';

在索引列上使用函数、表达式、隐式类型转换,都会导致索引失效,MySQL 只能走全表扫描。

开发者:"我在索引列上加了个函数,索引怎么不生效了?" MySQL:"你在我精心排好序的 B+ 树上做了变换,原来的顺序没了,我怎么用索引?" 开发者:"……好吧。"

7.4 前缀索引:对付长字符串

对于VARCHAR(255)这种长字符串字段,可以只索引前 N 个字符:

ALTER TABLE users ADD INDEX idx_email_prefix (email(10));

但前缀索引不能用于 ORDER BY 和覆盖索引,需要权衡。

7.5 索引不是越多越好

每个索引都是一棵独立的 B+ 树,需要额外的存储空间维护成本

  • 每次INSERTUPDATEDELETE都要同时更新所有相关的索引

  • 索引越多,写操作越慢

  • 一般建议单表索引不超过 5-6 个

DBA:"这个表有 12 个索引。" 开发者:"多索引多保险嘛。" DBA:"你每次插入一条数据,要同时维护 12 棵 B+ 树。你的 INSERT 语句在哭。"


八、总结:一张图看懂索引选型

数据结构查找时间范围查询排序适用场景
二叉搜索树O(log n)需遍历需遍历内存数据结构
红黑树/AVLO(log n)需遍历需遍历内存数据结构
Hash 表O(1)❌ 不支持❌ 不支持等值查询
B 树O(log n)中序遍历中序遍历文件系统
B+ 树O(log n)链表顺序扫描天然有序数据库索引

B+ 树胜出的原因总结:

  1. 🏔️矮胖:3-4 层就能存储上亿数据,磁盘 I/O 次数最少

  2. 🔗叶子节点链表:范围查询、排序、分组都是天然支持

  3. 📦非叶子节点不存数据:每个节点能存更多 key,树更矮

  4. 🎯查询稳定:每次查询都要走到叶子节点,性能可预测

  5. 💾磁盘友好:节点大小 = 页大小,完美利用预读机制


九、后记

下次当你的 SQL 查询飞快返回时,别忘了感谢那棵默默无闻的 B+ 树。它可能只有 3 层楼高,但每一层都是精心设计的。

当你的查询慢了,先看看EXPLAIN,看看索引有没有用上。大部分性能问题,都是索引没设计好。

记住:索引不是万能的,但没有索引是万万不能的。

DBA 的终极忠告: "先看 EXPLAIN,再看索引,最后才考虑加机器。" "如果你的 SQL 需要全表扫描,请先反思一下自己的人生选择。"


参考资源:

  • MySQL 官方文档 - B-Tree and Hash Indexes

  • Wikipedia - B+ Tree

  • Wikipedia - B-tree

  • InnoDB Diagrams Project (GitHub)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 6:35:49

机器人IPO审核新门槛:收入确认与技术壁垒双拷问

1. 一家机器人公司的上市临门一脚&#xff1a;为什么“宇树”这个名字突然刷屏最近几天&#xff0c;科技圈和投资圈的朋友可能都注意到了一个高频词——“宇树”。不是植物学里的某种灌木&#xff0c;也不是武侠小说里的隐世门派&#xff0c;而是杭州那家做四足机器狗的硬科技公…

作者头像 李华
网站建设 2026/7/17 6:35:37

C++17 std::pmr内存管理实战:从原理到性能优化

1. 项目概述如果你在C项目里被内存分配问题折磨过&#xff0c;比如性能瓶颈、内存碎片&#xff0c;或者想在特定场景&#xff08;比如嵌入式、高频交易、游戏引擎&#xff09;里精细控制内存&#xff0c;那么C17引入的std::pmr&#xff08;Polymorphic Memory Resource&#xf…

作者头像 李华
网站建设 2026/7/17 6:33:51

第13章:CesiumJS 从入门到精通13:时间的故事:CZML数据格式与时空可视化

本专栏正在连载中,欢迎关注不迷路~ 写在前面 前面章节我们通过原生 Entity API 手动构建动态三维对象,少量图形开发效率尚可,但面对成千上万个时序运动目标、前后端数据交互场景时,硬编码 JS 会极度臃肿、难以维护。 本章详解 Cesium 专属时空数据标准CZML(Cesium Langu…

作者头像 李华
网站建设 2026/7/17 6:33:38

Windows桌面图标混乱的根治方案与预防措施

1. 问题现象与根源分析每次开机后桌面图标排列混乱是Windows系统常见的顽疾之一。作为一名有十年系统维护经验的从业者&#xff0c;我处理过数百例类似案例。这种现象通常表现为&#xff1a;用户精心排列的桌面图标在重启后恢复默认排列&#xff0c;或者出现随机打乱的情况&…

作者头像 李华
网站建设 2026/7/17 6:31:51

Can Large Language Models Detect Real-World Android Software Compliance Violations?

文章核心总结与翻译 一、主要内容 本文聚焦大语言模型(LLMs)在Android软件合规违规检测中的应用,提出了全新评估框架CompliBench。该框架针对LGPD、PDPA、PIPEDA三类未被充分研究的数据保护法规,设计了两大核心任务:跨粒度检索定位(文件、模块、行级)和代码片段多标签…

作者头像 李华
网站建设 2026/7/17 6:30:18

高中物理成绩优异,适合报考大数据哪个细分专业?

这个问题挺有意思的。物理成绩好&#xff0c;说明你的逻辑思维、数学建模能力都不错。那在大数据相关的专业里&#xff0c;哪个最能把你的物理优势发挥出来&#xff1f;物理好的人&#xff0c;优势在哪&#xff1f;物理好的同学&#xff0c;通常具备三个特点&#xff1a;数学底…

作者头像 李华