B树和B+树

本文详细探讨了磁盘结构如何影响数据存储,从BST到B+Tree的索引演变,以及B-Trees和B+Trees的创建规则。重点讲解了稠密与稀疏索引的区别,以及在不同场景下的搜索树应用,如多路搜索树和B树的约束条件。

一、文章内容

从磁盘结构,数据如何存储到磁盘上到索引的由来,为什么使用多级索引。
m-way-tree => b-tree => b+tree
文章内容来自B站,一个有咖喱味的视频
文章中的扇区和视频中将的块有出入,参考了下网上的,好像磁盘的最小单位是扇区

二、磁盘结构

磁盘结构

(一)磁盘结构-同心圆一样的逻辑圈

  1. 逻辑圈 : 磁道(图中的蓝圈)是同心圆,从外向内一圈一圈的先是第一分区,然后是第二个分区是不错的,但是并不连续(逻辑连续但物理不连续)。
  2. 第一磁道 : 最外面的蓝圈 (0) 。
  3. 扇区 : 磁道和扇形区域相交的位置,如第一磁道和扇形 0 区相交的区域(绿色),扇区是磁盘的基本读写单位,通常是512字节。由于不断提高磁盘的大小,部分厂商设定每个扇区的大小是4k (4096字节) 。
  4. 假设 : 图中一共有24个扇区 (4磁道 * 6扇形) ,每个扇区的大小为512字节,共可以存储12k的数据(24*512/1024) 。

(二)磁盘读写

取图中的扇区 (绿色) 为例
扇区

  1. 0 ~ 511 : (一) 中假定扇区大小是 512 byte 。

  2. offset : 内存地址偏移量

  3. 内存地址 : 我们需要知道磁道号,扇区,和内存地址偏移量才能找到内存地址。

  4. 数据读写 : 数据读写

  5. 其他 : 有关磁盘柱面、寻址等相关信息之后再写。




三、索引

(一)对象存储和假设

数据库表

  1. Employee对象 : Employ对象中有eid,name,dept等域,假设每个employee对象占用128 bytes,其中 eid占10 bytes 。
  2. 数据库表 : 假设表中有100行数据,每行数据占128 bytes。
  3. 磁盘解耦 : 每个扇区能存储 512 位的数据。
  4. 100 行数据需要 100 * 128 / 512 = 25 个扇区来存储

所以,当我们查询时,最多需要访问25个扇区才能找到数据(读IO,就是发指令,从磁盘读取某段扇区的内容)


(二)稠密索引

稠密索引

  1. index数据表 : index每行都由 eid (employee对象的id - 之前假设占 10 bytes) 和 pointer指针 (指向数据库表中的employee 内存地址 - 假设占 6 bytes),所以index表每行占 16 bytes,共100行 。
  2. index 占用扇区 : index占用的扇区为16 * 100 / 512 = 3.125,需要4块扇区存储 (每块可以存储 512 / 16 = 32 行)。

所以, 100行数据需要25块扇区来存储, 而100行数据使用稠密索引,稠密索引需要用4块扇区来存储,此时数据查找最多需要访问5个扇区就能拿到所需数据的内存地址指针, 4 + 1 = 5次 IO 就能拿到数据


(三)稀疏索引

在数据指数级增长的情况下,稠密索引占用过多块,磁盘IO过多,以下图片省略磁盘结构部分 (记得稠密索引访问扇区时还需要进行一次 IO)
多级索引大致图

假设此时有1000个employee对象存到数据表中

  1. 数据占用扇区 : 每行数据占用 128 bytes,数据一共需要 1000 * 128 / 512 = 250 个扇区。
  2. 稠密索引表 : 每个索引行占用 16 bytes, 稠密索引表一共需要 1000 * 16 / 512 = 31.25 ≈ 32 个扇区,此时搜索速度下降,需要更高级的索引。
  3. 稀疏索引表 : 每个索引行占用 16 bytes, 其中每行的索引范围为 32,1000个稠密索引需要 1000 / 32 = 31.25 ≈ 32 行稀疏索引行,而32行 16 bytes的稀疏索引刚好为一个扇区。
  4. 此时查找数据如查询ID为50, 则从稀疏索引扇区 33 ~ 64 的索引行获取到稠密索引所在扇区的指针,从稠密索引扇区处获取指向ID为50的具体数据记录内存地址的指针,再访问该数据扇区。 一共三次 IO

所以,当我们查询时,最多需要访问25个磁盘才能找到数据(读IO,就是发指令,从磁盘读取某段扇区的内容)

四、搜索树

在线数据结构可视化工具

(一)BST - 二叉搜索树

BST

  1. 左子树上所有结点的值均小于或等于它的根结点的值
  2. 右子树上所有结点的值均大于或等于它的根结点的值
  3. 左右子树都是BST (Binary Search Trees)
  4. BST树种每个节点只有一个key,有两个子节点
  5. 超过一个以上key (关键字) 的节点 或 子节点超过两个以上 - 多路查找树

(二)muitl-way search tree - 多路搜索树

2-3树

  1. 每个节点最多有3个子节点
  2. 每个节点有2个key关键字

4-way search trees

  1. 4-way搜索树中有3个关键字,每个关键字都有对应指向记录的指针
  2. 4-way搜索树中有4个子节点,有4个指向子节点的指针
  3. muitl-way search tree 可能退化为链表,如下图
    退化成链表
    所以,为了防止这种情况出现,必须加上限制条件 ==> B-trees

(三)B-Trees

约束

假设度为M - 拥有最多孩子节点的节点所拥有的孩子节点的个数

  • 1)Root结点至少有两个子女
  • 2)每个非根节点所包含的关键字个数 j 满足:m/2 - 1 <= j <= m - 1。即当度为10,则非根节点的关键字必须在 4~9之间 (最大关键字个数 = M - 1 = 9)
  • 3)除根结点以外的所有结点(不包括叶子结点)的度数正好是关键字总数加1,故内部子树个数 k 满足:m/2 <= k <= m。即 2)中的关键字 + 1 也就是当度为10,关键字在 4 ~ 9之间,则子节点个数要 >=5
  • 4)叶子结点位于同一层
  • 5)创建的过程自下而上

创建过程

在线数据结构可视化工具
b树创建
每个节点上的关键字都有指向数据的指针,比如root节点的 20和50 (key) 都有指向数据记录的指针


(四)B+Trees

相比较B-Trees

  • B+树中,非叶子节点中的key关键字没有指向记录的指针
  • B+树中,仅叶子节点有指向记录数据的指针
  • B+树中,每个关键字都在叶子节点中有其副本,如下图
    M=4 的 B树和B+树插入10,20,30,40,50,60,70,80,90,100,11,21,31,41,51,61,71,81,91对比
    在这里插入图片描述
  1. B+树中的每个关键字在叶子节点中都有其副本
  2. B+树中的叶子节点有指向下一个节点的兄弟指针 (链表 - 稠密索引)
### B与B+的区别 BB+都是多路平衡查找,适用于外部存储大规模数据索引,但它们在结构功能上存在显著差异。 #### 1. **关键字与节点关系** B中,每个节点既可以存储关键字,也可以存储数据。对于一个m阶的B来说,每个节点最多包含m-1个关键字,同时子节点数量也最多为m。而B+的每个节点可以包含m个关键字,并且所有数据都存储在叶子节点中。非叶子节点仅存储索引信息,这使得B+更适用于大数据量的索引结构。 #### 2. **数据存储位置** B的数据可以存储在任意节点中,包括根节点、中间节点叶子节点。而B+的数据仅存储在叶子节点中,中间节点仅用于索引。这种设计使得B+能够更高效地进行批量数据访问。 #### 3. **分支节点的构造** B的分支节点存储关键字子节点指针,同时可能存储数据。而B+的分支节点仅存储关键字子节点指针,不存储实际数据。这意味着在相同的页大小(例如16KB)下,B+可以存储更多的索引信息,从而减少的高度,提高查询效率。 #### 4. **查询性能** B在查询时,可能在非叶子节点就找到所需数据,因此查询路径较短。但B+的查询路径固定,从根节点到叶子节点的高度一致,因此查询性能更加稳定。这种稳定性对于数据库索引尤为重要。 #### 5. **区间访问** B+的叶子节点通过链表连接,支持高效的区间查询。例如,数据库中的范围查询(如`SELECT * FROM table WHERE id BETWEEN 100 AND 200`)可以利用B+的这一特性快速完成。而B的叶子节点之间没有链接,区间查询效率较低。 #### 6. **磁盘I/O效率** 由于B+的非叶子节点不存储数据,每个节点可以容纳更多的关键字,从而减少的高度。这使得B+在磁盘I/O操作上更加高效,尤其适合需要频繁读取外部存储的场景。 ### B与B+的应用场景 #### B的应用场景 B适用于需要频繁更新查询的小规模数据集。由于B的非叶子节点存储数据,它在某些特定的场景中具有优势,例如: - **内存数据库**:当数据量较小且需要快速访问时,B可以减少磁盘I/O操作。 - **嵌入式系统**:在资源受限的环境中,B的简单结构可以提供较高的性能[^3]。 #### B+的应用场景 B+由于其结构特性,广泛应用于大规模数据存储索引系统,主要包括: - **数据库索引**:如MySQL的InnoDB引擎使用B+作为主键索引的底层实现,支持高效的查询范围扫描。 - **文件系统**:B+的叶子节点链表结构支持高效的文件顺序访问,例如在Linux文件系统中。 - **分布式存储系统**:B+的稳定查询性能高效的区间访问能力使其成为分布式数据库存储系统的首选索引结构[^2]。 ### 示例代码:B+的插入操作 以下是一个简化的B+插入操作的Python实现,用于展示B+的基本结构插入逻辑: ```python class BPlusTreeNode: def __init__(self, order, is_leaf): self.order = order self.is_leaf = is_leaf self.keys = [] self.children = [] def insert(self, key): if self.is_leaf: self.keys.append(key) self.keys.sort() else: child = self.get_child_for_key(key) child.insert(key) if len(child.keys) >= self.order: self.split_child(child) def split_child(self, child): new_node = BPlusTreeNode(child.order, child.is_leaf) mid = len(child.keys) // 2 new_node.keys = child.keys[mid:] child.keys = child.keys[:mid] if not child.is_leaf: new_node.children = child.children[mid:] child.children = child.children[:mid] self.keys.append(new_node.keys[0]) self.keys.sort() index = self.children.index(child) self.children.insert(index + 1, new_node) def get_child_for_key(self, key): for i, k in enumerate(self.keys): if key < k: return self.children[i] return self.children[-1] class BPlusTree: def __init__(self, order): self.order = order self.root = BPlusTreeNode(order, is_leaf=True) def insert(self, key): self.root.insert(key) if len(self.root.keys) >= self.order: new_root = BPlusTreeNode(self.order, is_leaf=False) new_root.keys = [self.root.keys[0]] new_root.children = [self.root] self.root = new_root # 示例:创建一个B+并插入几个键 tree = BPlusTree(order=4) for key in [10, 20, 5, 6, 15, 30]: tree.insert(key) ``` ###
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值