mysql-BTree和B+Tree详解


https://blog.csdn.net/weixin_41948075/article/details/100180136

 

常⻅的数组、链表、栈和队列都是线性结构,在存储⼤量数据时访问速度⽐较慢,⽽树(tree)则是⼀种⾮线性结构,使得访问时间复杂度降低到O(logn)。

下图是使⽤树结构存储的集合 {A,B,C,D,E,F,G,H,I,J,K,L,M,P,Q} 的示意图。对于数据 A 来说,和数据 B、C、D 有关系;对于数据 B 来说,和 E、F 有关系。这就是“⼀对多”的关系。将具有“⼀对多”关系的集合中的数据元素按照图下图的形式进⾏存储,整个存储形状在逻辑结构上看,类似于实际⽣活中倒着的树,所以称这种存储结构为“树型”存储结构。

 

⼀些概念

结点:树中⼀个最基本的数据称为⼀个节点(node) A-Q

根节点:以某个节点为⼀个根节点,从这个节点往下形成⼀棵⼦树(sub-tree)。 A

叶⼦结点: 整棵树最年轻的⼀代,在树中称为树的叶节点(leaf node)。通俗就是没有孩⼦的结点 B C H I K L M N P Q

每个⽗辈有⼏个孩⼦,每个孩⼦有其⽗辈,还有其祖辈。⽗辈称为⽗节点(parent),孩⼦称为⼦节点(child)。每个⽗节点可以对应⼀个或者多个⼦节点,

⼀个⼦节点只能有⼀个⽗节点。具有相同⽗节点的节点称为兄弟节点(siblings)。

结点的层次:根节点为第⼀层,根节点的孩⼦结点为第2层,依次类推

深度:树的层次的最⼤值

结点的度:结点拥有⼦树的数⽬

⼆叉树: 每个结点最多有2颗⼦树 ,简单地理解,满⾜以下两个条件的树就是⼆叉树:

1. 本身是有序树;

2. 树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2;

 

tree

满⼆叉树:除了叶⼦结点,每个节点都有2个孩⼦结点,且叶⼦节点在同⼀层上

完全⼆叉树:从树的根节点,从上⾄下,从左到右,依次填满结点形成的⼆叉树,满⼆叉树是⼀颗完全⼆叉树

⼆叉树的搜索速度取决于树的层级

B TREE(B-TREE)也就是平衡多路搜索树,也就是多叉的意思

当 数据库⾥单表存⻋处的数值可能是百万级或者千万级,如果有⼆叉树存储就会需要百万个千万个树节点,有没有办法让⼀个节点存储多个元素(数值),

那么,是不是可以减少树节点,从⽽减少树整体层级⾼度呢,要知道,减少树层级⾼度的过程,就是优化查询效率的过程。

所以,B树有出现了,B树属于多叉树,⼜名平衡多路查找树,数据库索引技术⾥⼤量使⽤是B树和B+树的数据结构,顾名思义,就是每个节点上可以存储多个数值元素,B树所有的叶⼦节点都处于同⼀层级。不存在层级⾼度差的问题。

 

每个节点保存的关键字的个数和路数关系为:关键字个数 = 路数 – 1。

B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进⾏⼆分查找,如果命中则结束,否则进⼊查询关键字所属范围的⼉⼦结点;重复,直到

所对应的⼉⼦指针为空,或已经是叶⼦结点;

 

实际磁盘举例:

来模拟下查找29的过程:

(1) 根据根结点指针找到⽂件⽬录的根磁盘块1,将其中的信息导⼊内存。 【磁盘IO操作1次】

(2) 此时内存中有两个⽂件名17,35和三个存储其他磁盘⻚⾯地址的数据。根据算法我们发现17<29<35,因此我们找到指针p2。

(3) 根据p2指针,我们定位到磁盘块3,并将其中的信息导⼊内存。 【磁盘IO操作2次】

(4) 此时内存中有两个⽂件名26,30和三个存储其他磁盘⻚⾯地址的数据。根据算法我们发现26<29<30,因此我们找到指针p2。 

(5) 根据p2指针,我们定位到磁盘块8,并将其中的信息导⼊内存。 【磁盘IO操作3次】 (6) 此时内存中有两个⽂件名28,29。根据算法我们查找到29,并定位了该⽂件内存的磁盘地址。

分析上⾯过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是⼀个有序表结构,可以利⽤⼆分法查找提⾼效率。⽽3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于平衡⼆叉树缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作⽤,从⽽提⾼了查询效率。

 

B Tree 能够很好的利⽤操作系统和磁盘的交互特性, MySQL为了很好的利⽤磁盘的预读能⼒,将⻚⼤⼩设置为16K,即将⼀个节点(磁盘块)的⼤⼩设置为16K,⼀次IO将⼀个节点(16K)内容加载进内存。这⾥,假设关键字类型为 int,即4字节,若每个关键字对应的数据区也为4字节,不考虑⼦节点引⽤的情况下,则上图中的每个节点⼤约能够存储(16 * 1000)/ 8 = 2000个关键字,共2001个路数。对于⼆叉树,三层⾼度,最多可以保存7个关键字,

⽽对于这种有2001路的B树,三层⾼度能够搜索的关键字个数远远的⼤于⼆叉树。这⾥顺便说⼀下:在B Tree保证树的平衡的过程中,每次关键字的变化,都会导致结构发⽣很⼤的变化,这个过程是特别浪费时间的,所以创建索引⼀定要创建合适的索引,⽽不是把所有的字段都创建索引,创建冗余索引只会在对数据进⾏新增,删除,修改时增加性能消耗。

B-Tree每个节点中不仅包含数据的key值,还有data值。⽽每⼀个⻚的存储空间是有限的,如果data数据较⼤时将会导致每个节点(即⼀个⻚)能存储的

key的数量很⼩,当存储的数据量很⼤时同样会导致B-Tree的深度较⼤,增⼤查询时的磁盘I/O次数,进⽽影响查询效率

 

B+Tree

B+树是B-树的变体,也是⼀种多路搜索树:

1.其定义基本与B-树同,除了:

2.⾮叶⼦结点的⼦树指针与关键字个数相同;

3.⾮叶⼦结点的⼦树指针P[i],指向关键字值属于[K[i], K[i+1])的⼦树(B-树是开区间);

5.为所有叶⼦结点增加⼀个链指针;

6.所有关键字都在叶⼦结点出现;

在B+Tree中,所有数据记录节点都是按照键值⼤⼩顺序存放在同⼀层的叶⼦节点上,⽽⾮叶⼦节点上只存储key值信息,这样可以⼤⼤加⼤每个节点存储

的key值数量,降低B+Tree的⾼度。

B+Tree相对于B-Tree有⼏点不同:

1. ⾮叶⼦节点只存储键值信息;

2. 所有叶⼦节点之间都有⼀个链指针;

3. 数据记录都存放在叶⼦节点中

通常在B+Tree上有两个头指针,⼀个指向根节点,另⼀个指向关键字最⼩的叶⼦节点,⽽且所有叶⼦节点(即数据节点)之间是⼀种链式环结构。因此可

以对B+Tree进⾏两种查找运算:⼀种是对于主键的范围查找和分⻚查找,另⼀种是从根节点开始,进⾏随机查找。

 

B TREE和B+TREE区别是什么?

1. B+Tree 关键字的搜索采⽤的是左闭合区间,之所以采⽤左闭合区间是因为他要最好的去⽀持⾃增id,这也是mysql的设计初衷。即,如果id = 1命中,会继续往下查找,直到找到叶⼦节点中的1。

2. B+Tree 根节点和⽀节点没有数据区,⾮叶⼦节点中只有关键字和指向下⼀个节点的索引,记录只放在叶⼦节点中。即只有叶⼦节点中的关键字数据区才会保存真正的数据内容或者是内容的地址。⽽在B树种,如果根节点命中,则会直接返回数据。B-树的关键字和记录是放在⼀起的,叶⼦节点可以看作外部节点,不包含任何信息

3. B+Tree叶⼦节点是顺序排列的,并且相邻的节点具有顺序引⽤的关系,如上图中叶⼦节点之间有指针相连接。

InnoDB存储引擎中⻚的⼤⼩为16KB,⼀般表的主键类型为INT(占⽤4个字节)或BIGINT(占⽤8个字节),指针类型也⼀般为4或8个字节,也就是说⼀个⻚(B+Tree中的⼀个节点)中⼤概存储16KB/(8B+8B)=1K个键值(因为是估值,为⽅便计算,这⾥的K取值为103)。也就是说⼀个深度为3的B+Tree索引可以维护103 * 10^3 * 10^3 = 10亿 条记录。

实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的⾼度⼀般都在2-4层。MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某⼀键值的⾏记录时最多只需要1~3次磁盘I/O操作。

 

B+Tree性质

1.所有关键字都出现在叶⼦结点的链表中(稠密索引),且链表中的关键字恰好是有序的;

2.不可能在⾮叶⼦结点命中;

3.⾮叶⼦结点相当于是叶⼦结点的索引(稀疏索引),叶⼦结点相当于是存储(关键字)数据的数据层;

4.更适合⽂件索引系统;

B*树

是B+树的变体,在B+树的⾮根和⾮叶⼦结点再增加指向兄弟的指针;

 

MySQL为什么最终要去选择B+Tree?

1. B+Tree是B TREE的变种,B TREE能解决的问题,B+TREE也能够解决(降低树的⾼度,增⼤节点存储数据量)

2. B+Tree扫库和扫表能⼒更强。如果我们要根据索引去进⾏数据表的扫描,对B TREE进⾏扫描,需要把整棵树遍历⼀遍,⽽B+TREE只需要遍历他的所有叶⼦节点即可(叶⼦节点之间有引⽤)。

3. B+TREE磁盘读写能⼒更强。他的根节点和⽀节点不保存数据区,所以根节点和⽀节点同样⼤⼩的情况下,保存的关键字要⽐B TREE要多。⽽叶⼦节点不保存⼦节点引⽤,能⽤于保存更多的关键字和数据。所以,B+TREE读写⼀次磁盘加载的关键字⽐B TREE更多。

4. B+Tree排序能⼒更强。上⾯的图中可以看出,B+Tree天然具有排序功能。

5. B+Tree查询性能稳定。B+Tree数据只保存在叶⼦节点,每次查询数据,查询IO次数⼀定是稳定的。当然这个每个⼈的理解都不同,因为在B TREE如果根节点命中直接返回,确实效率更⾼。

 

 

⼆叉树的遍历:分为⼴度优先和深度优先遍历

⼴度优先:按照树的层次,从第⼀次开始,⾃左⾄右遍历 BFS

深度优先,树的根结点为D,左⼦树为L,右⼦树为R,且要求L⼀定在R之前,分为前序、中序、后序遍历 DFS

前序遍历:根节点--左⼦树--右⼦树

中序遍历:左⼦树--根节点---右⼦树

后序遍历:左⼦树---右⼦树--根节点


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM