首先我们要知道Mysql如何衡量查询效率呢?—— 磁盘IO次数
一般来说索引非常大,尤其是关系性数据库这种数据量大的索引能达到亿级别,所以为了减少内存的占用,索引也会被存储在磁盘上。
数据库为啥不用二分查找和二叉树查找?
虽然这两种方法的查找效率提高了,但是各自对检索的数据都有要求:二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构。
学过数据库的都应该知道MySQL有两种索引:HASH索引和B树索引(B树索引的底层数据结构就是我们的主角——B+树
为什么不用二叉搜索树、红黑树?
我们来谈谈他们的共同缺点:
1.每个节点都只有一个索引值,而CPU每次从磁盘上至少读取1页(64位系统为4K)的数据,但是由于每个节点在磁盘中的位置是随机的,所以有很大概率CPU每次仅仅只能从磁盘读取到一个索引值。—> 没有很好的利用CPU与磁盘交互的特性。
2.正因为每个节点只能有一个索引值,所以无论二叉树、AVL树还是红黑树,当数据量达到十万、百万量级时,树的高度都会变得很高,从而会发生多次磁盘I/O —> 极端情况下,如果索引正好是顺序构建的,那二叉树就将成为链表(非平衡树的原因),AVL树和红黑树的高度也会变得很高很高【虽然红黑树可以解决链状树的问题,但是如果是大量数据的话还是无法避免树过高的问题】。如下图所示
【B树就是B-树,只是多了一个符号而已~】
所以,我们使用B树,B树是一种多路自平衡的搜索树 它类似普通的平衡二叉树,不同的一点是B树允许每个节点有更多的子节点。B-Tree 相对于AVL缩减了节点个数,使每次磁盘 I/O 取到内存的数据都发挥了作用,从而提高了查询效率。I/O渐进复杂度为O(n)
如图,如果要查找14,就需要查询2次,但是红黑树结构,查询次数明显会更多
红黑树:
谈谈B树
B-树/B+树的特点就是每层节点数目非常多,层数很少,目的就是为了减少磁盘IO次数,那MySQL的查询效率也就高了呀
我们来看看B树是怎么存储数据的
每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字
和三个指向子树根节点的指针
,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。
假设现在我们要找的是关键字29,过程应该如下:
根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
比较关键字29在区间(17,35),找到磁盘块1的指针P2。
根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
比较关键字29在区间(26,30),找到磁盘块3的指针P2。
根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
在磁盘块8中的关键字列表中找到关键字29。
分析上面过程,发现需要3次磁盘I/O
操作,和3次内存查找
操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。
那问题来了,B树那么好,我们为什么不使用B树,而是使用B+树呢?
B-树的每个节点都有data域,这无疑增大了节点大小,说白了增加了磁盘IO次数,为什么呢,因为磁盘IO一次读出的数据量大小是一个固定值,如果你的data数据和我的关键字一起的话,那一个节点所能存的关键字就少了是不是【也就是说如果你把存放data的空间用来存关键字,那是不是可能更快的找到我想要的关键字】,所以每次读出的就少,IO次数就要增多,一次IO多耗时呀)
而我们的B+树就是像上述说的【也就是说如果你把存放data的空间用来存关键字,那是不是可能更快的找到我想要的关键字】
它除了叶子节点其它节点并不存储数据,节点小,磁盘IO次数就少。So…我们现在来看看B+树
谈谈B+树
B+树中的B代表平衡(balance),而不是二叉(binary)。B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构。
从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值
,还有data值
。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息
,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
B+树就是对B树的一个优化,优化的三点内容如下:
- 非叶子节点data,只存储索引,可以放更多的索引。
- 所有叶子节点之间都有一个链指针(顺序访问指针,可以提高访问的性能)。
- 数据记录都存放在叶子节点中。
我们仍然用B树存储的那些数据来看看B+树改变后是怎样的
因为是例子,不可能给画特别多的结点来比较,所以我们做一个推算,用数学来体现B+树的优势
InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节)
这里使用BIGINT来计算,指针类型也一般为6个字节,也就是说根节点一个页中大概存储16KB/(8B+6B)=1170个键值
而叶子节点的一个页(因为InnoDB存储引擎叶子节点还要存储数据data)大概存储16KB/1KB = 16(假设为1KB=索引+数据)。也就是说一个深度为3的B+Tree索引可以维护1170 * 1170 * 16 = 2000多万条记录。所以当Mysql数据个数为2000多万(反正数据很大时)查询性能会急剧下降。
实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2 ~ 4层。MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘I/O操作。
B树和B+树的区别
- B+树的父节点和子节点之间存在数据冗余
- 正是因为父节点和子节点之间的数据冗余,所以叶子节点包含了所有非叶子节点的值,并且各个数值从左到右是按序排列的
- 叶子节点之间,从左到右都有一个指针进行连接 —> 即叶子节点之间是一个链表结构,并且其实mysql底层用到的B+树数据结构,叶子节点之间是一个双向链表。
这三点主要解决了一个问题:范围查找
如果我们需要找一个从X–>Y之间的数据,如果用B树的话,是不是得读取很多个磁盘的数据,过程会反反复复,这个可以自己画个图随便找两个数据试试,然后B+树的话,我们只要找到X,再根据叶子节点的指针直接找到Y之前的磁盘即可,因为叶子节点的关键值已经排好序了,所以十分方便。
参考:
https://www.cnblogs.com/will-xz/p/14381154.html
https://blog.csdn.net/dl962454/article/details/115800802
https://blog.csdn.net/nrsc272420199/article/details/104511925