从数据结构(树)深入理解数据库的索引

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/programmer_at/article/details/82735128

本文叙述的重点在于数据库的索引,因而,介绍树的这一章节不会面面俱到,而是着重强调了不同树之间的区别。在第三章节中,通过对比不同的索引结构和树,探讨“为什么要这样设计数据库索引”。

这篇文章从构思到完成前后花了一周,希望能够帮助自己对数据库索引这一知识点有更深理解。

1. 树

1.1二叉树

性质:
1. 在非空二叉树中,第 i 层的结点总数不超过 2 i 1 , i >= 1 ;
2. 深度为 h 的二叉树最多有 2 h 1 个结点(h>=1),最少有h个结点;
3. 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
4. 具有 n 个结点的完全二叉树的深度为 l o g 2 ( n + 1 ) ;
5. 给定N个节点,能构成h(N)种不同的二叉树,其中h(N)为卡特兰数的第N项,h(n)=C(2*n, n)/(n+1)。

Alt text

1.1.1 满二叉树

除叶子结点外的所有结点均有两个子结点。节点数达到最大值,所有叶子结点必须在同一层上。

性质:
1. 一颗树深度为h,最大层数为k,深度与最大层数相同,k=h;
2. 叶子数为2h;
3. 第k层的结点数是: 2 k 1 ;
4. 总结点数是: 2 k 1 ,且总节点数一定是奇数。

1.1.2 完全二叉树

若设二叉树的深度为h,除第 h 层外,其它各层 (1~(h-1)层) 的结点数都达到最大个数,第h层所有的结点都连续集中在最左边,这就是完全二叉树。

完全二叉树是效率很高的数据结构,堆是一种完全二叉树或近似完全二叉树。

1.1.3 二叉查找树(BST)

二叉排序树(Binary Sort Tree)或二叉搜索树。

二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2. 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
3. 左、右子树也分别为二叉排序树;
4. 没有键值相等的节点。
5. 对二叉查找树进行中序遍历,即可得到有序的数列。
6. 插入和查找的时间复杂度均为O(logn),但是在最坏的情况下仍然会有O(n)的时间复杂度

1.1.4 平衡二叉树(AVL)

AVL树解决了BST退化成链表的问题。
它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
在平衡二叉搜索树中,插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。
平衡二叉树的常用算法有红黑树、AVL树等。

红黑树

红黑树与AVL树相比,红黑树并不是高度平衡的,它放弃了高度平衡的特性而只追求部分平衡,这种特性降低了插入、删除时对树旋转的要求,从而提升了树的整体性能。

红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。
性质:
1. 节点是红色或黑色。
2. 根是黑色。
3. 所有叶子都是黑色(叶子是NIL节点)。
4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

这里写图片描述

1.2 B树与B+树

B树的非叶子节点包含关键字和指向关键字具体信息的指针

这里写图片描述

这里写图片描述
B+树是B树的变体,二者的异同:
1. 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息)
2. 所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针 (而B 树的叶子节点并没有包括全部需要查找的信息)

这里“B树的叶子节点不包含任何关键字信息”,关键是把什么当作叶子节点,有两种解读:
- 叶子节点是存在的且非空,内含关系的全部信息,但是不具有孩子节点了。所以,“关键字信息”意为孩子节点了。
- B树的叶子节点为null, 那么叶子节点的确是不具有任何关键字信息。

  1. 叶子结点本身依关键字的大小自小而大的顺序链接(B树则没有)。
    这里写图片描述

2. MySQL中常见的索引类型

索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。

一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。

2.1 FULLTEXT

全文检索,MyISAM引擎支持, InnoDB(version 5.5之后)开始支持。
可以在CHAR,VARCHAR,TEXT列上创建全文索引。

先将数据填充到表中,再建立FULLTEXT索引的效率要比先建立FULLTEXT索引再导入数据要快。

它的出现是为了解决WHERE name LIKE “%word%”这类针对文本的模糊查询效率较低的问题。在没有全文索引之前,这样一个查询语句是要进行遍历数据表操作的,可见,在数据量较大时是极其的耗时的,如果没有异步IO处理,进程将被挟持,很浪费时间。

2.2 HASH

哈希索引,MyISAM,InnoDB均不支持,Memory引擎支持。但InnoDB支持自适应索引(InnoDB 存储引擎会监控对表上索引的查找,如果观察到建立哈希索引可以带来速度的提升,则建立哈希索引)。

可以为一列或几列建立hash索引时,将这一列或几列的值通过一定的算法计算出一个hash值。这个哈希值对应一行或多行数据(hash冲突)。将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中。hash索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。

hash索引 vs. B+树索引
1. hash索引只能等值过滤(=, IN, <=, >=),而不需要使用范围查询。
2. hash索引不能排序操作
hash值大小与键值大小没有关系
3. hash索引不能利用部分索引键查询
对于组合索引,是将多列的数据组合在一起计算hash值,而不是每列数据单独计算hash值在拼接在一起的。通过组合索引的前一个或其中几个索引键来查询时,hash索引会失效。
4. hash 索引不能避免表扫描。
由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果
5. Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。

2.3 B+树

树索引,MyISAM和InnoDB默认索引。
MyISAM的索引的叶子节点存放的是指针,指向数据文件对应的数据行。
InnoDB的索引的叶子节点存放的数据。

InnoDB索引和MyISAM索引的区别:
一是主索引的区别,InnoDB的数据文件本身就是索引文件。而MyISAM的索引和数据是分开的。
二是辅助索引的区别:InnoDB的辅助索引data域存储相应记录主键的值而不是地址。而MyISAM的辅助索引和主索引没有多大区别。

2.3.1 InnoDB

  1. 主键索引
    而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引
    这里写图片描述
  2. 辅助索引
    InnoDB的所有辅助索引都引用主键作为data域。辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
    例如,下图为定义在Col3上的一个辅助索引:
    这里写图片描述

2.3.2 MyISAM

MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。

  1. 主键索引
    这里设表一共有三列,假设我们以Col1为主键,图myisam1是一个MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件仅仅保存数据记录的地址。
    Alt text

  2. 辅助索引
    在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。如果我们在Col2上建立一个辅助索引,则此索引的结构如下图所示:
    Alt text

3. 进阶面试题

3.1 MySQL数据库中的索引为什么能够加快查询速度?

  • MySQL中的索引是B+树,使用B+树查找数据的时间复杂度度为O(logN).
  • 索引中的叶子节点是连在一起的,这会加快范围查询。
    如下图所示(InnoDB),查找订单号在18到49的全部订单。首先通过从根节点开始查找,找到订单号为18的叶子节点,然后从当前的叶子节点向后查找,直到找到订单号为49的订单才停止查找。遍历的叶子节点皆作为查询结果返回。

3.2 倘若将索引改成B树,BST,AVL, 或红黑树,可以吗?

  • B+树 vs. B树
    - 减少磁盘访问次数
    B树的非叶子节点包含了关键字和指向关键字具体信息的指针,而叶子节点不含任何关键信息;而B+树非叶子节点不含指向关键字的指针,只是叶子节点关键字的索引,叶子节点才包含指向关键字信息。因此,B+树内部节点比B树小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
    - 查询效率更加稳定
    B树搜索可能在非叶子节点结束,也靠近根节点的记录查找时间越短,而查询不存在的关键字,仍需要从根节点走到叶子节点才能确定。而在B+树中,任何关键字的查找都必须走一条从根节点到叶节点的路径,查找路径相同,导致每个关键字查询效率相当。尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些。
    - B树不支持顺序遍历
    B+树的叶子节点使用指针顺序连接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而数据库中的范围查询是非常频繁的,而B树顺序遍历效率比较低。
  • 二分查找(BST)树
    BST树的深度不可控,最糟糕的情况是BST变成单向链表,深度较大的BST不但会增加磁盘IO,也会影响查找效率。
  • 平衡二叉树(AVL)
    与BST相比,虽然能够保证绝对平衡,保证了查找效率,但是维护AVL树平衡的开销比较大,尤其对于插入删除频繁的业务场景而言,绝对平衡带来的查找效率优势就不再那么明显了。
  • 红黑树
    红黑树虽然解决了AVL树维护平衡开销比较大的问题,但是红黑树跟B树一样,非叶子节点也会存储数据,同样面临跟B树同样的问题,磁盘IO负担大。此外,红黑树分支因子比较少,深度比B+树更深,那么在查找时页缺失的概率也随之增大。

3.3 哈希(hash)比树(tree)更快,索引结构为什么要设计成树型?

索引设计成树形,和SQL的需求相关。
对于这样一个单行查询的SQL需求:select * from t where name=”shenjian”;
确实是哈希索引更快,因为每次都只查询一条记录。所以,如果业务需求都是单行访问,例如passport,确实可以使用哈希索引。
但是对于排序查询的SQL需求:分组:group by; 排序:order by; 比较:<、>
哈希型的索引,时间复杂度会退化为O(n),而树型的“有序”特性,依然能够保持O(log(n)) 的高效率。

Ref:
https://blog.csdn.net/jaryle/article/details/52023295
https://dev.mysql.com/doc/refman/5.6/en/storage-engines.html
https://www.cnblogs.com/xyxxs/p/4440187.html
http://blog.codinglabs.org/articles/theory-of-mysql-index.html (从磁盘IO角度讲解了B+树索引的性能)
https://blog.csdn.net/v_JULY_v/article/details/6530142 (实例讲解使用B+树索引时磁盘IO的情况)

猜你喜欢

转载自blog.csdn.net/programmer_at/article/details/82735128