面试必问的数据库-3.1:索引-底层实现原理

索引可以加快查询的效率。

如果数据库没有加索引,sql中where是怎么执行查找到目标的?

举例:

如果想要查询表格第二列是怎么查找的?

如想查询where col2=22的记录,在没有加索引的情况下是按顺序从第一条记录开始查找,

因此需要查找5次才能找到22。

如果对col2列家伙是哪个索引之后,

假设用最简单的二叉树作为索引存储的方式,

再次查找where col2=22的记录,这次只需要查找两次就能找到目标记录,效果提高非常明显。

1:二叉树

二叉树是一种比顺序结构更加高效的结构,

它从第一个父节点开始跟目标元素值比较,

如果相等,则返回当前节点,

如果目标元素小于当前节点,则移动到左侧子节点进行比较,

大于的话,移动到右侧子节点比较。

反复进行操作,最终移动到目标元素节点位置。

优点:

相对于顺序查找来说,效率高了很多,提升了查询效率。

缺点:

在大部分情况下,我们设计索引时都会在表中提供一个自增整形字段作为建立索引的列,在这种场景下使用二叉树的结构会导致我们的索引总是添加到右侧,在查找记录时跟没加索引的情况是一样的。

这种情况会导致将导致二叉排序树的效率大大降低。

:

为了避免这种情况的发生,我们希望可以有一种算法,将我们的不平衡的二叉排序树转化为平衡二叉排序树。

这样就可以让我们的二叉排序树结构最优化。

2:平衡二叉树(AVL 树)

在符合二叉查找树的条件下,还满足任何节点的两个子树的高度最大差为 1,但不是红黑树。

那么如何构建平衡二叉树呢?

在二叉树不停地 插入数据的时候,有可能导致失衡,此时就需要对该二叉树进行旋转操作,使之变成平衡二叉树。

那么怎么判断当插入一条数据二叉树失衡呢?

根据平衡二叉树的性质判断(左右树高度差最大为1,始终左子树的数据都大于该节点,右子树小于改节点)

比如说:当插入一个新的数据,某一个节点,的左子树中有一个数据大于该节点,

此时就违反了平衡二叉树的性质,就要旋转,

怎么旋转呢,

把最新插入的那个节点作为替代刚才那个节点。

举例:

依次插入数据:1,23,45,34,98,9,4,35,23

至于平衡二叉树中几个旋转的叫法,不必详细看,大概知道反正是要旋转才能保证平衡就行了。

3:红黑树

3.1:2-3树

再说红黑树之前,必须得了解下红黑树的由来,也就是红黑树的起源。

之前已经说过了,二叉树的极限就是一列链表了,它有一个问题,就是容易偏向某一侧,这样就像一个链表结构了。

失去了树结构的优点,查找时间会变坏。

所以我们都希望树结构都是矮矮胖胖,像这样:

而不是像这样:

在这种需求下,平衡树的概念就应运而生了。也就是上面说的平衡二叉树出现了。

而红黑树就是一种平衡树,它可以保证二叉树基本符合矮矮胖胖的结构。

然而什么是2-3树呢?

2-3树是二叉查找树的变种,树中的2和3代表两种节点,以下表示为2-节点和3-节点。

2-节点即普通节点包含一个元素,两条子链接。

3-节点则是扩充版包含2个元素和三条链接:两个元素A、B,左边的链接指向小于A的节点,中间的链接指向介于A、B值之间的节点,右边的链接指向大于B的节点。

2节点:

3节点:

在这两种节点的配合下,2-3树可以保证在插入值过程中,任意叶子节点到根节点的距离都是相同的。完全实现了矮胖矮胖的目标。

(我之后会在出一个2-3树的构造过程,或者网上有很多这种博客,具体的你们可以去看,这里只是说明红黑树的演变过程)

缺点:

由于要维护3节点,因此实现起来不方便,因为要处理的情况太多。

这样需要维护两种不同类型的节点,将链接和其他信息从一个节点复制到另一个节点,将节点从一种类型转换为另一种类型等等。

因此:

红黑树出现了,红黑树的背后逻辑就是2-3树的逻辑,但是由于用红黑作为标记这个小技巧,最后实现的代码量并不大。(但是,要直接理解这些代码是如何工作的以及背后的道理,就比较困难了。所以你一定要理解它的演化过程,才能真正的理解红黑树)

我们来看看红黑树和2-3树的关联,首先,最台面上的问题,红和黑的含义。红黑树中,所有的节点都是标准的2-节点,为了体现出3-节点,这里将3-节点的两个元素用左斜红色的链接连接起来,即连接了两个2-节点来表示一个3-节点。这里红色节点标记就代表指向其的链接是红链接,黑色标记的节点就是普通的节点。所以才会有那样一条定义,叫“从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点”,因为红色节点是可以与其父节点合并为一个3-节点的,红黑树实现的其实是一个完美的黑色平衡,如果你将红黑树中所有的红色链接放平,那么它所有的叶子节点到根节点的距离都是相同的。所以它并不是一个严格的平衡二叉树,但是它的综合性能已经很优秀了。

所以,红黑树的另一种定义是满足下列条件的二叉查找树:

⑴红链接均为左链接。

⑵没有任何一个结点同时和两条红链接相连。(这样会出现4-节点)

⑶该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同。

3.2:红黑树

 红黑树就是一种平衡的二叉查找树,说他平衡的意思是它不会变成“瘸子“,

左腿特别长,或者右腿特别长(线性结构)。

怎么说呢?

1:红黑树放弃了追求完全平衡,追求大致平衡。

保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。

2:平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,

每次插入新节点之后需要旋转的次数不能预知。

业务场景:

平衡二叉树(AVL)树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转非常耗时的。所以平衡二叉树(AVL)适合用于插入与删除次数比较少,但查找多的情况。

红黑树在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。所以红黑树适用于搜索,插入,删除操作较多的情况。

红黑树当插入或者更删除节点,就会对平衡造成破坏,这个时候就要对树进行调整,从而重新达到平衡。

左旋或者右旋。

其实平衡二叉树也是各种璇,看到人头大。

具体红黑树和二叉树是怎么璇以达到平衡,其实不用太过在意(可能上学的时候,这块是重点,现在都忘得差不多了,如果要具体理解这块的话,可以百度搜索相关的博客,里面将的都非常详细),只需要知道有这么个东西。

优点:

红黑树是一种特殊的平衡二叉树,有平衡二叉树的某些优点,也解决了二叉树为链表的那种情况。

因为红黑树是一种页数的平衡二叉树,在插入数据的时候,和平衡二叉树一样,会对结构进行调整,调整为平衡二叉树。

但是始终保证 左子节点 < 父节点 < 右子节点 的规则。

缺点:

在数据量大的时候,深度也很大。

每个父节点只能存在两个子节点,如果我们有很多数据,那么树的深度依然会很大,可能就会超过十几二十层以上,对我们的磁盘寻址不利,依然会花费很多时间查找。

再次说明下红黑树和平衡二叉树的区别:

平衡二叉树是高度平衡的,频繁的插入和删除,会引起频繁的再平衡,导致效率下降。

红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。

红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。

红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
红黑树和AVL树的区别在于它使用颜色来标识结点的高度,它所追求的是局部平衡而不是AVL树中的非常严格的平衡。

那怎么解决红黑树的缺点呢?在数据量大的时候,深度也很大,查询也会不方便。

4:Hash

优点:

对数据进行Hash(散列)运算,主流的Hash算法有MD5、SHA256等等,然后将哈希结果作为文件指针,可以从索引文件中获得数据的文件指针,再到数据文件中获取到数据。

按照这样的设计,我们在查找where Col2 = 22的记录时只需要对22做哈希运算得到该索引所对应那行数据的文件指针,从而在MySQL的数据文件中定位到目标记录,查询效率非常高。
 

缺点:

无法解决范围查询查询的场景,比如 select count(id) from sus_user where id >10;

因此Hash这种索引结构只能针对字段名=目标值的场景使用。

不适合模糊查询(like)的场景。

 

此时,又为了解决hash中不能范围查询的缺点,怎么办呢?

 

5:B-Tree(也叫B树)

B-tree就是指的B树

既然红黑树存在缺点,那么我们可以在红黑树的基础上构思一种新的储存结构。

解决的思路也很简单,既然觉得树的深度太长,就只需要适当地增加每个树节点能存储的数据个数即可,但是数据个数也必须要设定一个合理的阈值,不然一个节点数据个数过多会产生多余的消耗。

按照这样的思路,我们先来了解下关于B-Tree的一些知识点:

1:叶节点具有相同的深度,左子树跟右子树的深度一致

2:叶节点的指针为空

3:节点中的数据key从左到右递增排列

在这里需要说明下的是,BTree的结构里每个节点包含了索引值表记录(数据值)的信息,

我们可以按照Map集合这样理解:key=索引,value=表记录,如下图所示:

优点:

BTree的结构可以弥补红黑树的缺点,解决数据量过大时整棵树的深度过长的问题

相同数量的数据只需要更少的层,相同深度的树可以存储更多的数据,查找的效率自然会更高。

缺点:

在查询单条数据是非常快的。

但如果范围查的话,BTree结构每次都要从根节点查询一遍,效率会有所降低,

因此在实际应用中采用的是另一种BTree的变种B+Tree(B+树)。

 

因为btree树的叶子节点不是连续的。就像链表那样。

另外,由于插入删除新的数据记录会破坏B-Tree的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作(反正就是和平衡二叉树和红黑树一样各种旋转)以保持B-Tree性质。

注意:

这里B树还是没有解决上面那个要范围查询的 问题,只是解决了红黑树深度太深的问题(B树扩展了节点的广度)。

所以为了解决范围查询的 问题,就有了B+Tree树的出现?

6:B+Tree(MySQL索引的真正存储结构)

B+Tree是B树的一种变种,B树有很多变种,具体有哪些这里就不说了。

在介绍B+Tree之前,我们先来看下面两个问题:

1. 为什么要对BTree继续做优化?

要解答这个疑问需要先了解BTree每个节点结构和MySQL数据库它是如何读取索引数据的,

索引和表数据在不使用的时候是存储在文件中的,也就是磁盘

当我们执行查询操作时会DBMS(数据库管理系统)首先会先从内存中查找,如果找到直接使用,如果找不到则从磁盘文件中读取

操作系统储存数据的最小单位是页(page),一页假设是4K大小(由操作系统决定),对内存和磁盘读取数据是按一页的整数倍读取的。

这个其实是一个什么区间读取的定理。(具体叫什么,可以查看如何从磁盘读取数据)

也就是说,我读取某一个数据的时候,不单单从磁盘上只读这一个数据,不然太浪费io了,所以我把从该数据开始,往后一页page的数据都读到内存中,万一读之后有用到呢,不就是可以减少了大量的io操作了吗。(估计是这样理解的,就算不是,也八九不离十)

这里我们假设数据库一次IO操作就读取1页4K的数据,再假设图中圈起来的元素就是一个大节点,内含多个小节点的索引和数据,其大小是10MB,那么我们要从磁盘中读取完整个大节点需要进行 10M / 4K = 2500次IO操作,这样就可以看出如果大节点数据总量越大需要执行的IO操作越多花费的时间也越长,因此为了提高性能,数据库会建议我们一个大节点只存储一页4K大小的数据,这里的数据包含了索引和表记录,另外我们还能计算出树的度Degree应该设置成多大才合理:

Degree(树的深度) = 内存页大小(4K) / 单个索引值字节大小;

进一步分析,索引值的大小相对于整条记录的大小是很小的,如果我们需要查找的数据刚好是在最后,那么前面遍历过的节点中存储的记录数据是不是对我们来说是没用的,它会占用比索引大得多的空间,导致我们一个大节点里能遍历的索引数量大大减少,需要向下继续遍历的几率就更大,花费更多时间查找,那么有没有办法可以优化呢?看下一个个问题。

2. 相对于BTree,B+Tree做了哪些优化?

B+Tree存储结构,只有叶子节点存储数据

新的B+树结构没有在所有的节点里存储记录数据,而是只在最下层的叶子节点存储数据

上层的所有非叶子节点只存放索引信息,这样的结构可以让单个节点存放下更多索引值,增大度Degree的值,提高命中目标记录的几率。

这种结构会在上层非叶子节点存储一部分冗余数据,但是这样的缺点都是可以容忍的,因为冗余的都是索引数据,不会对内存造成大的负担。

每个叶子节点都指向下一个叶子节点(就像链表一样)

(叶子节点两两指针相互链接,符合磁盘的预读特性,顺序查询性能更高)

这点优化有什么用呢?

如果我们进行范围查找where id > 4的记录,

我们只需要先找到id = 4的记录后自然就能通过叶子节点间的双向指针方便地查询出大于4的所有记录。

1:B+树每个节点可以包含更多的节点,这样做有两个原因,一个是降低树的高度。另外一个是将数据范围变为多个区间,区间越多,数据检索越快

2:非叶子节点存储key,叶子节点存储key和数据

3:叶子节点两两指针相互链接(符合磁盘的预读特性),顺序查询性能更高

 

注:MySQL 的 InnoDB 存储引擎在设计时是将根节点常驻内存的,因此力求达到树的深度不超过 3,也就是说 I/O 不需要超过 3 次。

 

基本上也就这些了,至于mysql索引的一些知识:

比如索引分类,哪些情况导致索引失效,等等

之后我会在写一个博客整理下。

猜你喜欢

转载自blog.csdn.net/u010953880/article/details/88220194