B+树的生成过程 怎么去看懂B+树

本文介绍了InnoDB存储引擎中B+树的原理,包括页大小、数据排序、主键作用以及页目录的重要性。通过实例展示了数据插入过程中的排序现象,解释了为何数据会自动按主键排序,即使未设置主键,InnoDB也会自动生成。B+树的特性使得数据查询效率提高,减少了磁盘IO次数。同时,讨论了页目录如何辅助快速定位数据,以及B+树的结构优化。

前提

看B+树 看不懂树结构什么意思 这一篇可以帮你理解树结构生成的过程

在说B+ 树之前 需要知道 一页的大小是多少

show global status like 'innodb_page_size'

这个是看出 一页是 16384 也就是 16384/1024 = 16kb innodb中一页的大小默认是 16kb

正文

创建表结构 指定引擎为 Innodb

CREATE TABLE tree(
id int PRIMARY key auto_increment,
t_name VARCHAR(20),
t_code int 
) ENGINE=INNODB
复制代码
复制代码

查看一下当前表的索引情况

show index from tree

B树和B+树的显示都是BTREE 但是实际使用的B+树 但是B+树也是B树的升级版 称之为B树也是没有问题的

创建数据 这里会有一个小知识点 如果看过上一篇文章的朋友可以明白是为什么


INSERT into tree VALUES(3,"变成派大星",3);
INSERT into tree VALUES(1,"变成派大星",1);
INSERT into tree VALUES(2,"变成派大星",2);
INSERT into tree VALUES(4,"变成派大星",4);
INSERT into tree VALUES(7,"变成派大星",7);
INSERT into tree VALUES(5,"变成派大星",5);
INSERT into tree VALUES(6,"变成派大星",6);
INSERT into tree VALUES(8,"变成派大星",8);
复制代码
复制代码

疑问

为什么创建数据的时候数据是乱的但是在创建好数据 被拍好顺序了

基础知识

我们在寻找答案之前 想明白一些基础知识

细心的朋友可以看出来 我们插入Id 时候数据是乱的 插入进去之后 数据就自动帮我通过Id 进行排序了 这是为什么呢?接着往下看

我们如果对于B+树有点了解的话就知道B+树是每页16KB 进行数据储存 在进行数据查询的时候也是一页一页的去查询

相当于下面的数据

首先每一页都有很多数据 就像 我们平常去写分页的时候我们返回给前端的数据也会有很多属性

这个可能比较抽象 我是把他当成平常 分页查询的思想代入进去

我们可以把一页想成是一个对象

@Data
public class page {

List<UserRecords> data;

....省略其余属性
}
复制代码
复制代码

我们先看一下 一页数据的图是什么样子 仅仅是进行逻辑思考画的图

这里的Data 就相当于 一页中的数据区域

但是这里是有限制的 上面我们说到 一页的数据只能是16Kb 也就是 一个Page 里面的data只能16Kb 数据 当超过16Kb 就会新开一个对象相当于在进行创建树的时候增加了判断

Java代码思路模拟:

当 Page 对象的大小已经达到16Kb 就算完成这一页 把这一页放到 磁盘中等待使用就行了 到时候进行查询数据的时候会直接返回这一页 里面包含这些数据

我们回到最初的问题 为什么我们在进行插入的时候明明Id 是乱的 等到插入到数据的时候 数据就变成有序的了 我们知道 同时这个数据是根据主键进行排序的 也就Innobd 的数据储存一定是要依赖主键的 有些人会想 我就是不创建主键 他还能排序吗?

疑问二

我们在疑问一的基础上 产生出的疑问 不设置主键 Mysql怎么办

解答

InnoDB对聚簇索引处理如下:

  • 如果定义了主键,那么InnoDB会使用主键作为聚簇索引
  • 如果没有定义主键,那么会使用第一非空的唯一索引(NOT NULL and UNIQUE INDEX)作为聚簇索引
  • 如果既没有主键也找不到合适的非空索引,InnoDB会自动帮你创建一个不可见的、长度为6字节的row_id,而且InnoDB 维护了一个全局的 dictsys.row_id,所以未定义主键的表都共享该row_id,每次插入一条数据,都把全局row_id当成主键id,然后全局row_id加1

很明显,缺少主键的表,InnoDB会内置一列用于聚簇索引来组织数据。而没有建立主键的话就没法通过主键来进行索引,查询的时候都是全表扫描,小数据量没问题,大数据量就会出现性能问题。

但是,问题真的只是查询影响吗?不是的,对于生成的ROW_ID,其自增的实现来源于一个全局的序列,而所以有ROW_ID的表共享该序列,这也意味着插入的时候生成需要共享一个序列,那么高并发插入的时候为了保持唯一性就避免不了锁的竞争,进而影响性能

解答

我们看完疑问二的解答就知道 即便我们不设置主键 数据也会帮我们去生成一个默认的主键 有点像 类默认生成构造器的思想

有了主键之后呢?

为什么会自动排序 大家都知道了 其实在文章之初就会有很多人明白是为什么 大概脑子里会有答案

疑问三

为什么要进行排序

解答

我们都知道 在进行数据查找的时候 比如几个基础的查找算法的 前提都是 先进行排序 再者 List 和 Map 的一些区别肯定都很熟悉了 当然是为了更快 所以无需的Id 会对插入效率造成影响 也就是为什么很多文章说使用自增Id比UUID 或者雪花算效率高的原因 第一个是UUID他们是随机的 每次都要重新排序 甚至可能会因为排序的原因造成页数据的更换 还有就是 UUID 一般都比较长 一页是16Kb 数据越短 一页的数据就会越多 查询的速度也就比较快

这里说完为什么排序 还有一个点就是上面的页目录

疑问三

页目录的作用是什么?

通过上一篇文章可以明白 页目录的作用是减少范围

这里的第三层是数据 上面都是目录 可以增加数据的检索效率

如果没有目录我们需要去直接遍历数据区域 会降低效率 目录能帮我们缩小范围 这里 我们查询 ID = 3 我们可以通过目录知道 1 < 3 < 4 如果 在1 中没有找到对应数据 但是 因为 3 < 4 就不会接着往下查询了 直接返回空结果

当第一页没有的时候去第二页查询 不会直接跳到第二页查询

为了提高效率 当目录数据数量过多时 就会网上延伸一层树 同时可以减少磁盘的IO次数

关于所有叶子节点都处于同一深度是如何实现的?这与B+树具体的插入和删除算法有关。简单解释一下插入时的情况,根据插入值的大小,逐步向下直到对应的叶子节点。如果叶子节点关键字个数小于2t,则直接插入值或者更新卫星数据;如果插入之前叶子节点已经满了,则分裂该叶子节点成两半,并把中间值提上到父节点的关键字中,如果这导致父节点满了的话,则把该父节点分裂,如此递归向上。所以树高是一层层的增加的,叶子节点永远都在同一深度。

小总结

  • 内部节点并不存储真正的信息,而是保存其叶子节点的最小值作为索引。
  • 每次插入删除都进行更新(此时用到parent指针),保持最新状态。
  • B+ 树非叶子节点上是不存储数据的,仅存储键值
  • B+只在底层树储存数据,上层就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的 IO 次数又会再次减少,数据查询的效率也会更快。
  • B+ 树的阶数是等于键值的数量的,如果我们的 B+ 树一个节点可以存储 1000 个键值,那么 3 层 B+ 树可以存储 1000×1000×1000=10 亿个数据。
  • 一般根节点是常驻内存的,所以一般我们查找 10 亿数据,只需要 2 次磁盘 IO。
  • 因为 B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。
  • 那么 B+ 树使得范围查找,排序查找,分组查找以及去重查找变得异常简单
  • 有心的读者可能还发现上图 B+ 树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。
  • 其实上面的 B 树我们也可以对各个节点加上链表。这些不是它们之前的区别,是因为在 MySQL 的 InnoDB 存储引擎中,索引就是这样存储的。
  • 我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。
<think>我们正在讨论如何阅读和理解C++代码。根据用户的需求,我将提供一些步骤和技巧来帮助用户逐步掌握阅读和理解C++代码的方法。首先,阅读C++代码需要一定的C++语言基础。如果用户是初学者,建议先学习C++的基本语法,如变量、数据类型、控制结构(if/else,for,while等)、函数、类、模板等。然后,我们可以按照以下步骤来阅读和理解C++代码:1.**理解代码的结构**:C++代码通常由头文件(.h或.hpp)和源文件(.cpp)组成。头文件包含类声明、函数声明、宏定义等,而源文件包含具体的实现。首先,查看代码的组织结构,了解各个文件的作用。2.**从main函数开始**:对于可执行程序,程序的入口是main函数。找到main函数,从这里开始阅读,可以了解程序的整体流程。3.**跟踪函数调用**:在阅读过程中,遇到函数调用,可以跳转到该函数的定义处,理解该函数的功能。注意函数的参数和返回值。4.**理解类的设计**:如果代码是面向对象的,那么理解类的层次结构和设计非常重要。查看类的成员变量和成员函数,以及类之间的关系(继承、组合等)。5.**注意代码中的注释**:良好的注释可以帮助理解代码。但注意,注释可能不是最新的,所以要以代码为准。6.**使用工具辅助阅读**:-**IDE(集成开发环境)**:如VisualStudio、CLion等,它们提供了代码导航(如跳转到定义、查找引用)、代码高亮、调试等功能,极大提高阅读效率。-**调试器**:通过调试器单步执行代码,观察变量的变化,可以帮助理解程序的执行流程。-**静态分析工具**:如Doxygen可以生成代码文档,帮助理解代码结构。-**日志输出**:在代码中添加日志输出(如使用RLOG[^2]),可以帮助跟踪程序运行的行为。7.**阅读相关文档**:如果有设计文档、需求文档或API文档,应该结合文档来理解代码。8.**分模块阅读**:如果代码规模较大,不要试图一次性理解全部代码。可以分模块阅读,先理解每个模块的功能,再理解模块之间的交互。9.**实践**:尝试修改代码,添加一些功能或修复一些bug,通过实际操作加深理解。10.**代码审查(CodeReview)**:如果可能,参与代码审查,与其他开发者讨论代码,可以快速提高理解能力。此外,引用[2]中提到了一些工具,如RLOG(一个轻量级的日志库)和堆栈跟踪工具,这些工具在调试和理解代码非常有用。例如,在代码中添加日志输出可以帮助我们跟踪函数的调用顺序和变量的值。引用[3]中提到了源代码的作用和重要性,强调了可读性。因此,我们在阅读代码,也要注意代码的可读性,包括良好的命名、适当的注释和合理的结构。最后,阅读和理解C++代码是一个渐进的过程,需要耐心和实践。对于复杂的代码,可能需要多次阅读和调试才能完全理解。下面,我将生成一些相关问题,帮助用户进一步深入学习。§§相关问题§§1.如何提高C++代码的可读性?2.在阅读大型C++项目,有哪些有效的策略?3.如何使用调试器(如GDB)来理解C++代码的执行流程?4.C++中的模板和泛型编程如何影响代码的阅读和理解?5.有哪些工具可以帮助自动生成C++代码文档?</think>### 如何阅读和理解 C++ 代码 阅读和理解 C++ 代码需要系统的方法和工具支持。以下是分步指南: #### **1. 基础准备** - **掌握语法基础** 熟悉核心语法:变量、数据类型、控制流(`if/for/while`)、函数、类、模板、指针等。 示例:理解指针操作 `int* ptr = &var;` 需明确 `ptr` 存储内存地址,`*ptr` 访问值。 - **搭建开发环境** 使用 IDE(如 CLion、VS Code)或编辑器配合调试器(GDB),提供代码跳转、语法高亮、实错误检查功能。 #### **2. 代码结构分析** - **文件组织结构** C++ 项目通常包含: - 头文件(`.h/.hpp`):类声明、函数原型、宏定义(如 `#define MAX_SIZE 100`)。 - 源文件(`.cpp`):函数实现、全局变量定义。 - 关键:从 `main()` 函数切入,跟踪程序入口逻辑。 - **模块化阅读** 将代码按功能模块分解(如输入处理、算法核心、输出模块),避免同理解整个项目。 > *示例:图像处理程序可分为“加载图像→滤波处理→保存结果”模块。* #### **3. 工具辅助理解** - **调试与日志** - **添加日志输出**:使用轻量级库(如 RLOG[^2])记录变量状态和函数调用路径。 ```cpp #include <rlog/rlog.h> int main() { rDebug("程序启动"); int x = calculate(); // 调试日志会输出 x 的值 } ``` - **堆栈跟踪**:用 GDB 设置断点(`break line_num`),通过 `backtrace` 查看调用栈。 - **文档生成工具** 用 Doxygen 解析注释生成文档: ```cpp /// @brief 计算两数之和 /// @param a 整数参数1 int add(int a, int b) { return a + b; } ``` 生成 HTML 文档可直观查看函数关系。 #### **4. 进阶技巧** - **理解面向对象设计** - 类图分析:绘制类之间的关系(继承 `: public Base`、组合 `class A { B b; }`)。 - 虚函数:通过 `virtual` 关键字识别多态行为。 - **模板与泛型** 模板代码(如 `std::vector<T>`)需代入具体类型分析: ```cpp template<typename T> void swap(T& a, T& b) { T tmp = a; a = b; b = tmp; } // 实际调用 T 被替换为 int/string 等 ``` - **内存管理** 关注 `new/delete` 配对,使用 Valgrind 检测内存泄漏。 #### **5. 实践策略** - **代码注释**:为复杂逻辑添加中文注释,明确意图(如 `// 此处使用快速排序优化性能`)。 - **小步修改**:通过微调代码(如修改变量值)观察行为变化,验证理解。 - **Code Review**:参与开源项目(如 GitHub 的 C++ 项目),学习他人代码结构[^2]。 > **关键提示**:C++ 的复杂性在于底层控制(如指针操作)和抽象机制(模板、继承)。建议结合《C++ Primer》等经典资料系统性学习[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值