教你通过B+Tree平衡多叉树理解InnoDB引擎的聚集和非聚集索引

目录
  • InnoDB引擎是通过B+Tree实现索引结构
  • 二叉树(Binary Tree)
  • 平衡二叉树(AVL Tree)
  • 平衡多叉树(B-Tree)
  • B+Tree
  • 聚集和非聚集索引
    • 聚集索引(clustered index)
    • 非聚集索引(secondary index)

InnoDB引擎是通过B+Tree实现索引结构

因为B+Tree是从二叉树演化而来,在这之前我们来先了解二叉树、平衡二叉树、平衡多叉树。

二叉树(Binary Tree)

简介:每个节点最多有两个子树的树结构。
它有一个特点:左子树的键值小于根的键值,右子树的键值大于根的键值。

如下图(图1)所示就是一个二叉树:

对于该二叉树,假设我们要查找键值为5的节点:

首先从根节点开始,
第一次查找结果为6判断5小于6;
往下查找6的左节点,结果为3判断5大于3;
往下查找3的右节点,结果为5。
一共查找了3次。

我们可以发现:

深度为1的节点需要查找一次,这样的节点有1个需要查找1次;
深度为2的节点需要查找2次,这样的节点有2个共需要查找4次;
深度为3的节点需要查找3次,这样的节点有3个共需要查找9次。
如果要将所有节点全部查找遍历一共需要查找1+4+9=14次。

二叉树可以任意地构造,同样是2、3、5、6、7、8这几个数字,还可以按照下图(图2)的方式构造:

如果要遍历该二叉树所有节点一共需要查找1+2+3+4+5+5=20次,这样效率相对来说就比较低了。
若要最大程度地减少二叉树的查找次数,需要二叉树尽可能地趋近于平衡二叉树。

平衡二叉树(AVL Tree)

简介:在满足二叉树的前提下任一节点上左右两个子树的深度差不超过1。

下面两个二叉树,左边的是平衡二叉树,右边的是非平衡二叉树:

如果在平衡二叉树中插入或删除节点,可能会导致平衡二叉树失衡。
这种失去平衡的二叉树可以概括为四种姿态:LL(左左)、RR(右右)、LR(左右)、RL(右左)。

它们的示意图如下:

这四种失去平衡的姿态都有各自的定义:
LL:LeftLeft,也称“左左”。
插入或删除一个节点后,根节点的左孩子(Left Child)的左孩子(Left Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。
RR:RightRight,也称“右右”。
插入或删除一个节点后,根节点的右孩子(Right Child)的右孩子(Right Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。
LR:LeftRight,也称“左右”。
插入或删除一个节点后,根节点的左孩子(Left Child)的右孩子(Right Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。
RL:RightLeft,也称“右左”。
插入或删除一个节点后,根节点的右孩子(Right Child)的左孩子(Left Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。
平衡二叉树失衡后,可以通过旋转二叉树使其恢复平衡。具体这四种失去平衡姿态情况下对应的旋转方法,请移步BTree和B+Tree详解,里面有十分详细的讲解,这里不再赘述。

平衡多叉树(B-Tree)

B-Tree是为磁盘等设备设计的一种平衡查找树,因此在讲B-Tree之前先了解下磁盘的相关知识。

系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。

InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB,可通过参数innodb_page_size将页的大小设置为4K、8K、16K,
在MySQL中可通过如下命令查看页的大小:

mysql> show variables like 'innodb_page_size';

而系统一个磁盘块的存储空间往往没有这么大,因此InnoDB每次申请磁盘空间时都会是若干个地址连续磁盘块(成为大磁盘块即页,接下来的部分如无特殊说明,所说磁盘块即是InnoDB的页)来达到页的大小16KB。
InnoDB在把磁盘数据读取到内存时会以页为基本单位。

B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。
为了描述B-Tree,首先定义一条记录为一个对象[key, data],key为记录的键值,对应表中的主键,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。

m(整数)阶(宽度为m,m>2)B-Tree满足以下条件:

每个节点占用一个磁盘块;
节点(除叶节点外)包含指针、键值(关键字)、数据三种元素,叶节点不包含其它节点的关键字信息:即指针;
所有叶子节点都在同一层;
节点拥有n个按照升序排序的不连续关键字,n在[m/2, m-1]闭区间内为整数;
除叶节点外,n个关键字划分出n+1个范围域对应n+1个指针。指针指向子树关键字的范围域,
比如:以根节点为例,关键字为17和35,p1指针指向的子树的数据范围域小于17,p2指针指向的子树的数据范围域在(17, 35)开区间内,p3指针指向的子树的数据范围域大于35。

见下图:
磁盘块3也是包含三个指针,只是p1和p3的指向磁盘块没有画出来。

如果要查找关键字29,
那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,
在内存中用二分法查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,
通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,
29在26和30之间,锁定磁盘块3的P2指针,
通过指针加载磁盘块8到内存,发生第三次IO,
同时内存中做二分查找找到29,结束查询,总计三次IO。


基础·磁盘IO与预读:
磁盘读取依靠的是机械运动,分为寻道时间、旋转延迟、传输时间三个部分,
这三个部分耗时相加就是一次磁盘IO的时间,大概9ms左右。这个成本是访问内存的十万倍左右;
正是由于磁盘IO是非常昂贵的操作,所以计算机操作系统对此做了优化:
预读:每一次IO时,不仅仅把当前磁盘地址的数据加载到内存,同时也把相邻数据也加载到内存缓冲区中。
因为局部预读原理说明,当访问一个地址数据的时候,与其相邻的数据很快也会被访问到。
每次磁盘IO读取的数据我们称之为一页(page)。一页的大小与操作系统有关,一般为4k或者8k。这也就意味着读取一页内数据的时候,实际上发生了一次磁盘IO。

我们再来看二叉树的查找过程

定义一个树高为4的二叉树,查找值为10:

第一次磁盘IO:

第二次磁盘IO:

第三次磁盘IO:

第四次磁盘IO:

可以看到最坏情况下,磁盘的IO次数等于VAL树的高度。

二叉树深度和节点数的关系:

相比VAL二叉树,B-Tree可以大大减少磁盘IO次数。

B+Tree

B+tree是基于B-tree的变体。

B-Tree结构中每个节点中不仅包含数据的key值,还有data值,而每一个节点的存储空间是有限的,
如果data数据比较大会导致每个节点能存储的key的数量变少,导致数据量很大时B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。
在B+Tree中,所有数据记录节点都存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。

和B-Tree不同,由于它的非叶子节点不存储data信息,所以相同的键值会和data一起,出现在叶子节点中。
以磁盘块3的p2指针为例,指针指向的子树的数据范围域就是在在[36, 79)左开右闭区间内。

InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,
一个页(B+Tree中的一个节点)中大概能存储16KB/(8B+8B)=1K个键值(为方便计算,这里的K取值为[10]^3)。
也就是说一个深度为3的B+Tree索引大概可以维护10^3 * 10^3 * 10^3 = 10亿条记录。

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

聚集和非聚集索引

到这儿,再来理解聚集和非聚集索引就比较简单了。

聚集索引(clustered index)

通常,聚集索引是主键的同义词。一个表中只能拥有一个聚集索引。
聚集索引的逻辑顺序与数据库表中记录在磁盘上的排列顺序一致。通俗来讲就是,聚集索引叶子节点中存储的数据包含表中一条记录完整的信息。找到键值key就找到了整条数据。

非聚集索引(secondary index)

非聚集索引相对来说就不是那么好理解一点。
因为前面我们讲到的关键值key对应的都是数据库中唯一的一条记录,而非聚集索引不一样,它的关键值key无法确定唯一一条记录,多条记录的key值可能会相同。
非聚集索引是通过溢出页来处理这种情况的,当多条记录对应某个同一关键值key时,分配出一个溢出页,
用来存放重复键值及其对应记录的偏移量(该条记录在磁盘上的实际物理位置)。和HashMap解决Hash key冲突类似。
非聚集索引维护了表中记录的逻辑顺序,但是它的叶子节点存储的是表中的记录在数据页中的指针,相比聚集索引会多一次磁盘IO。

以上见解如有不当之处,请予指正。

参考文章
过来啊小莲BTree和B+Tree详解
lbcBoy索引原理-BTree讲解

还有两篇找不到了,找齐之后会在这里补上。

补充参考文章
SeedQiB-Tree详解

到此这篇关于通过B+Tree平衡多叉树理解InnoDB引擎的聚集和非聚集索引的文章就介绍到这了,更多相关B+Tree平衡多叉树内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 教你通过B+Tree平衡多叉树理解InnoDB引擎的聚集和非聚集索引

    目录 InnoDB引擎是通过B+Tree实现索引结构 二叉树(Binary Tree) 平衡二叉树(AVL Tree) 平衡多叉树(B-Tree) B+Tree 聚集和非聚集索引 聚集索引(clustered index) 非聚集索引(secondary index) InnoDB引擎是通过B+Tree实现索引结构 因为B+Tree是从二叉树演化而来,在这之前我们来先了解二叉树.平衡二叉树.平衡多叉树. 二叉树(Binary Tree) 简介:每个节点最多有两个子树的树结构.它有一个特点:左子树

  • 解析MySQL join查询的原理

    MySQL用Nested-Loop Join算法实现join查询 区分驱动表和被驱动表,以驱动表的结果集为循环的基础,访问被驱动表过滤数据,然后合并结果,驱动表在外循环.被驱动表在内循环.如果还有第三张参与join查询的表,则以合并的结果为驱动表,第三张表作为被驱动表,以此类推. left join中的左表是驱动表.右表是被驱动表,right join刚好相反. Nested-Loop Join有三种实现 SNLJ Simple Nested-Loop Join 假设A是驱动表,B是被驱动表.

  • MySQL数据库开发的36条原则(小结)

    前言 这些原则都是经历过实战总结而成 每一条原则背后都是血淋淋的教训 这些原则主要是针对数据库开发人员,在开发过程中务必注意 一.核心原则 1.尽量不在数据库做运算 俗话说:别让脚趾头想事情,那是脑瓜子的职责 作为数据库开发人员,我们应该让数据库多做她所擅长的事情: 尽量不在数据库做运算 复杂运算移到程序端CPU 尽可能简单应用MYSQL 举例: 在mysql中尽量不要使用如:md5().Order by Rand()等这类运算函数 2.尽量控制单表数据量 大家都知道单表数据量过大后会影响数据查

  • MySQL20个高性能架构设计原则(值得收藏)

    开源数据库架构设计原则 01. 技术选型 选择成熟的平台和技术,同时是最熟悉的,能做到极致的,用好不用坏,用熟不用生.目前业界的MySQL主流分支版本有Oracle官方版本的MySQL.Percona Server.MariaDB. 02. 高可用选择 高可用解决方案探讨的本质上是低宕机时间解决方案,可以理解成高可用的反面是不可用,绝大部分情况下数据库宕机才会导致数据库不可用.随着技术发展,开源数据库方面很多高可用组件(主从复制.半同步.MGR.MHA.Galera Cluster),对应场景,

  • MySQL索引原理详解

    目录 索引是什么 索引数据结构 树形索引 树的动画 为什么不是简单的二叉树? 为什么不是红黑树? 为什么最终选择B+树 而不是B树 水平方向可以存放更多的索引key 数据量估算 叶子节点包含所有的索引字段 叶子节点直接包含双向指针,范围查找效率高 Hash 索引 更快 不支持范围查询 hash 冲突问题 表引擎 MyISAM 和 InnoDB 引擎 MyISAM 引擎 InnoDB 表数据组织形式 聚集与非聚集索引 ★★★ 为什么建议InnoDB 表必须有主键,并且是整型自增的? 为什么是整型

  • 深入理解 MySQL 索引底层原理

    目录 Mysql 索引底层数据结构选型 哈希表(Hash) 二叉查找树(BST) AVL 树和红黑树 B 树 5.B+树 Innodb 引擎和 Myisam 引擎的实现 MyISAM 引擎的底层实现(非聚集索引方式) Innodb 引擎的底层实现(聚集索引方式) 一步一步推导出 Mysql 索引的底层数据结构. Mysql 作为互联网中非常热门的数据库,其底层的存储引擎和数据检索引擎的设计非常重要,尤其是 Mysql 数据的存储形式以及索引的设计,决定了 Mysql 整体的数据检索性能. 我们知

  • Oracle索引(B*tree与Bitmap)的学习总结

    在Oracle中,索引基本分为以下几种:B*Tree索引,反向索引,降序索引,位图索引,函数索引,interMedia全文索引等,其中最常用的是B*Tree索引和Bitmap索引.(1).与索引相关视图查询DBA_INDEXES视图可得到表中所有索引的列表:访问USER_IND_COLUMNS视图可得到一个给定表中被索引的特定列.(2).组合索引概念当某个索引包含有多个已索引的列时,称这个索引为组合(concatented)索引.注意:只有在使用到索引的前导索引时才可以使用组合索引(3).B*T

  • webpack4 CSS Tree Shaking的使用

    本次课程的代码目录(如下图所示): 什么是tree-shaking webpack 2 的到来带来的最棒的新特性之一就是tree-shaking .tree-shaking源自于rollup.js,先如今,webpack 2也有类似的做法. webpack 里的tree-shaking的到来不得不归功于es6规范的模块.为什么这么说,如今的前端模块规范很多,比较出流行的比如commonJS , AMD , es6 ,我简单的说一下commonJS和es6模块的区别. 1. CSS 也有 Tree

  • 获取 MySQL innodb B+tree 的高度的方法

    前言 MySQL 的 innodb 引擎之所以使用 B+tree 来存储索引,就是想尽量减少数据查询时磁盘 IO 次数.树的高度直接影响了查询的性能.一般树的高度在 3~4 层较为适宜.数据库分表的目的也是为了控制树的高度.那么如何获取树的高度呢?下面使用一个示例来说明如何获取树的高度. 示例数据准备 建表语句如下: CREATE TABLE `user` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `name` varchar(100) CHARAC

  • 基于B-树和B+树的使用:数据搜索和数据库索引的详细介绍

    B-树 1 .B-树定义 B-树是一种平衡的多路查找树,它在文件系统中很有用. 定义:一棵m 阶的B-树,或者为空树,或为满足下列特性的m 叉树:⑴树中每个结点至多有m 棵子树:⑵若根结点不是叶子结点,则至少有两棵子树: ⑶除根结点之外的所有非终端结点至少有[m/2] 棵子树:⑷所有的非终端结点中包含以下信息数据: (n,A0,K1,A1,K2,-,Kn,An)其中:Ki(i=1,2,-,n)为关键码,且Ki<Ki+1, Ai 为指向子树根结点的指针(i=0,1,-,n),且指针Ai-1 所指子

随机推荐