索引的原理——我们为什么用B+树来做索引?

本文详细解释了数据库索引的概念,为何使用索引及其在提高查询效率中的作用。对比了不同数据结构,如数组、二叉树、B树和B+树在数据库索引中的适用性和优劣,重点阐述了B+树如何成为数据库索引的首选。

什么是索引?

索引是关系数据库中对某一列或多个列的值进行预排序的数据结构,就好比书籍的目录。

为什么使用索引?

索引的出现其实就是为了提高数据库查询的效率,就好比书的目录,通过书的目录,我们可以快速精确找到我们想要的信息

不适合索引的数据结构

索引的作用是为了提高查询的效率,而可以提高查询的数据结构有很多种,我们都知道数据库选择了B+树这一数据模型作为索引的数据结构,那么为什么选择B+树,而不是其他数据结构?

数组

如果单从查询效率来看,数组显然是最合适的,我们都知道数组的查询速度快,但是当我们的数据库中存在数据更新时,假如在中间插入一个数据,那么后面的全部数据都要移动,显然数组结构比较适合静态数据不变的存储

二叉树

由二分查找思想,二叉树也是经典的提高查询速度的数据结构,但是为什么不选择二叉树是因为,索引的存储位置原因,这里首先问一下,索引存储在哪里?
索引存储在磁盘当中,首先要知道索引要加载到内存中才能查询,而从磁盘到内存的加载过程就会有磁盘I/O过程,数据查询的主要时间就是磁盘I/O的时间,我们简单理解就是I/O过程非常消耗时间,我们需要做的就是尽量减少I/O的过程,这里我们举个例子。假设一个7个节点的二叉树它可以是三层高,也同样可以是7层高,二叉树高度不好控不行,那么平衡二叉树那,我们认为的添加限定条件,限制树的高度,但是假如N比较大二叉树依然很高,所以我们换一种思路M叉树,也就是B树和B+树

B-树

B-树是一种平衡的多路查找树。B树结构如下图所示
B树结构
B树每个结点最多有M棵子树,M就是B树的阶,一棵M阶的B树满足下面的特性:

  1. 树中每个节点最多有M棵子树
  2. 若根节点不是叶子结点,则最少有两棵子树
  3. 除根节点外的所有非终端结点,也就是中间结点,子树在[ceil(M/2),M]之间,就是有最大最小值约束
  4. 所有非终端结点(叶子结点)也就是中间结点,包含三种信息(关键字,子树,指针),假设一个中间结点,包含K个关键字,则子树数目为k+1,也就是说关键字和子树数目存在加一的关系
  5. 假设中间节点节点的关键字为:Key[1], Key[2], …, Key[k-1],且关键字按照升序排序,即 Key[i]<Key[i+1]。此时 k-1 个关键字相当于划分了 k 个范围,也就是对应着 k个指针,即为:P[1], P[2], …, P[k],其中 P[1] 指向关键字小于 Key[1] 的子树,P[i] 指向关键字属于 (Key[i-1], Key[i]) 的子树,P[k] 指向关键字大于 Key[k-1] 的子树
  6. 所有叶子结点出现在同一个层次

B+树

B+树是基于B-树做的改进,两者差异在于:

  1. 有 k 个孩子的节点就有 k 个关键字。也就是孩子数量 = 关键字数,而 B 树中,孩子数量 = 关键字数 +1。
  2. 非叶子节点的关键字也会同时存在在子节点中,并且是在子节点中所有关键字的最大(或最小)。
  3. 非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。而B树中,非叶子节点既保存索引,也保存数据记录。
  4. 所有关键字都在叶子节点出现,叶子节点构成一个有序链表,而且叶子节点本身按照关
    键字的大小从小到大顺序链接。下图就是一棵 B+ 树,阶数为 3,根节点中的关键字 1、18、35 分别是子节点(1,8,14),(18,24,31)和(35,41,53)中的最小值。每一层父节点的关键字都会出现在下一层的子节点的关键字中,因此在叶子节点中包括了所有的关键字信息,并且每一个叶子节点都有一个指向下一个节点的指针,这样就形成了一个链表。
    在这里插入图片描述
    在B树和B+树两者之间,我们为什么选择B+树了那,因为局部性原理和磁盘预读的特性,这两个概念读者可以自行百度一下,是计算机系统的特性,计算机读取数据是以页为单位的,大小为4K,所以说我们每次读取磁盘块大小固定的,如果我们在上层不仅存储关键字还存储数据,那么就导致我们虽然可以有多个关键字指代多个子树,但是我们空间不够,导致我们依然存不了几个子树数据。
    总结一下就是,首先B+ 树查询效率更稳定。因为 B+ 树每次只有访问到叶子节点才能找到对应的数据,而在 B 树中,非叶子节点也会存储数据,这样就会造成查询效率不稳定的情况,有时候访问到了非叶子节点就可以找到关键字,而有时需要访问到叶子节点才能找到关键字。
    其次,B+ 树的查询效率更高,这是因为通常 B+ 树比 B 树更矮胖(阶数更大,深度更低),查询所需要的磁盘 I/O 也会更少。同样的磁盘页大小,B+ 树可以存储更多的节点关键字。
    不仅是对单个关键字的查询上,在查询范围上,B+ 树的效率也比 B 树高。这是因为所有关键字都出现在 B+ 树的叶子节点中,并通过有序链表进行了链接。而在 B 树中则需要通过中序遍历才能完成查询范围的查找,效率要低很多。
<think>嗯,用户想深入理解MySQL索引为什么采用B+作为数据结构。这个问题在数据库领域非常经典,用户可能正在学习数据库索引原理,或是遇到了性能优化问题需要溯源。从用户连续追问索引结构的专注度看,应该是技术背景较强的开发者或DBA。 用户引用的四篇资料都聚焦在B+的优势上,但角度各有侧重:引用[1]和[4]强调磁盘I/O优化,引用[2]对比了其他数据结构,引用[3]则点出存储引擎的实现差异。需要把这些信息整合成体系化的解释。 核心需要讲清楚三个层面的问题:首先是B+本身的物理特性(高、节点大小),然后是与其他数据结构的对比优势(特别是B和哈希),最后要结合MySQL存储设计(如InnoDB页机制)。用户之前的回复里已经提到过B+三层能存1600万数据,这次可以补充计算过程。 特别要注意平衡理论和实践场景。比如不仅要解释“为什么适合”,还要说明“什么情况下优势会削弱”——例如当索引键过长时B+深度增加,或是全内存场景下其他结构可能更优。用户似乎很关注技术决策的trade-off思维。 回复结构可以这样组织:先简述B+结构特征,然后用对比表格突出其优势,最后补充实际应用时的注意事项。数学公式部分要完整呈现节点容量计算,但需用文字解释每个变量的含义。磁盘I/O次数与高的关系需要重点说明,这是用户引用[1][2]反复提到的关键点。</think>MySQL 选择 B+ 作为索引结构的核心原因在于其**卓越的磁盘 I/O 性能**和**对数据库查询场景的深度适配**。以下是关键原因分析: --- ### 一、B+的核心优势 #### 1. **极低的磁盘I/O次数** - **节点容量大**: 每个节点存储大量键值(默认16KB页大小),大幅减少的高度。 计算公式(以主键BIGINT为例): $$ \text{单节点指针数} = \frac{16\text{KB}}{8\text{B(键值)} + 6\text{B(指针)}} \approx 1170 $$ $$ \text{三层存储量} = 1170 \times 1170 \times 1170 \approx 1600\text{万行} $$ **仅需3次磁盘I/O即可检索千万级数据**[^1][^3]。 - **对比B**: B非叶节点存储数据,相同数据量下高更高 → 更多磁盘I/O。 #### 2. **高效范围查询** ```mermaid graph LR A[范围查询] --> B[定位起始键值] B --> C[沿叶节点链表顺序扫描] C --> D[无需回溯父节点] ``` - 叶节点形成**双向链表**,范围查询(如`WHERE id BETWEEN 100 AND 200`)只需: 1. 定位起始键值(3次I/O) 2. 沿链表顺序读取($O(1)$跳转) - **对比B**:范围查询需回溯父节点,产生随机I/O。 #### 3. **数据存储优化** - **非叶节点不存数据**: 仅存储键值+指针 → 单节点容纳更多键值 → 进一步降低高。 - **叶节点存储全集**: 所有数据均存于叶节点 → 查询路径长度稳定($O(\log n)$)。 --- ### 二、与其他数据结构的对比 | **结构** | **范围查询** | **磁盘I/O** | **数据更新代价** | **适用场景** | |----------------|-------------|------------|-----------------|-------------------| | **B+** | ✅ 极优 | ✅ 3层千万级 | 中(需平衡) | 通用OLTP | | **B** | ❌ 需回溯 | 高更高 | 高(节点存数据) | 内存数据库 | | **哈希表** | ❌ 不支持 | $O(1)$ | 低 | 等值查询 | | **红黑** | ✅ 支持 | $O(\log n)$| 高(旋转频繁) | 内存索引 | > ⚠️ **关键结论**: > - **磁盘型数据库**:B+在I/O效率、范围查询、稳定性上全面占优[^1][^2] > - **内存型数据库**:哈希或红黑可能更合适 --- ### 三、与MySQL架构的深度契合 #### 1. **适配InnoDB存储引擎** - **聚集索引**: 叶节点直接存储行数据 → 主键查询仅需1次检索[^3]。 - **辅助索引**: 叶节点存储主键值 → 更新数据时辅助索引无需修改。 #### 2. **页分裂优化** - 当叶节点满时(填充因子=15/16),触发页分裂: ```mermaid graph TB A[节点满] --> B[分配新页] B --> C[迁移50%数据] C --> D[更新父节点指针] ``` - **B+分裂代价低于B**: 数据仅存于叶节点 → 分裂不涉及非叶层数据迁移。 #### 3. **覆盖索引支持** - 联合索引`(col1, col2)`存储`col1值+col2值` - 查询`SELECT col1, col2`时**无需回表** → 直接返回索引数据。 --- ### 四、实际性能影响案例 ```sql -- 场景:1亿条用户数据查询 SELECT * FROM users WHERE age BETWEEN 25 AND 30; ``` - **B+执行过程**: 1. 根节点→中间节点→定位首个age=25的叶节点(3次I/O) 2. 沿链表顺序扫描至age=30(顺序I/O,约5-10次) - **若用B**: 每跳转一个节点需随机I/O → 预计20+次I/O(性能差5倍以上) --- > 💡 **设计启示**: > - **主键自增**:避免随机写入导致页分裂 > - **控制索引长度**:过长的键值会减少单节点键数量 → 增加高 > - **覆盖索引优先**:减少回表查询的I/O开销
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值