十分钟轻松掌握索引的数据结构

索引的数据结构

⭐️写在前面

这里是允谦的学习之路
如果对你有帮助,给博主一个免费的点赞以示鼓励把QAQ
博客主页 允谦的学习小屋
⭐️更多文章‍请关注允谦主页
文章发布日期:2022.02.23
java学习之路!
欢迎各位点赞评论收藏⭐️
冲冲冲

文章目录

  • 索引的数据结构
    • 1、索引及其优缺点
      • 1.1索引的优缺点
      • 1.2 优点:
      • 1.3 缺点:
    • 2、InnoDB中索引的推演
      • 2.1索引之前的查找
        • 1、在一个页中的查找
        • 2、在很多页中查找
      • 2.2 设计索引
        • 1、一个简单的索引设计方案
      • 2.3 常见索引概念
        • 1、聚簇索引
        • 2、二级索引(辅助索引、非聚簇索引)
        • 3、联合索引
      • 2.4 InnoDB的B+索引的注意事项
    • 3、MySQL数据结构选择的合理性
      • 3.1 Hash结构
      • 3.2 二叉搜索树
      • 3.3 AVL树
      • 3.4 B-树
      • 3.5 B+Tree
      • 3.5 B+Tree

1、索引及其优缺点

1.1索引的优缺点

MySQL官方对索引的定义:索引是帮助和MySQL搞笑获取数据的数据结构。

索引的本质:索引是数据结构,你可以简单理解为“排好序的快速查找数据结构”,满足特定查找算法。这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法

索引是在存储引擎中实现的,因此每种存储引擎的索引不一定完全相同,并且每种存储引擎不一定支持所有索引类型。同时,存储引擎可以定义为每个表的最大索引数最大索引长度。所有存储引擎支持每个彪啊至少16个索引,总索引长度最少为256字节。

1.2 优点:

  • 降低数据库IO成本,这也是创建索引最主要的原因
  • 通过创建唯一索引,可以保证数据库中每一行数据的唯一性
  • 在实现数据的参考完整性方面,可以加速表与表之间的连接
  • 使用分组和排序子句进行数据查询时,可以显著减少查询中分组和排序的时间,降低CPU的消耗

1.3 缺点:

  1. 创建索引和维护索引要耗费时间,并且随着数据量的增加,所耗费的时间也会增加
  2. 索引需要占磁盘空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,存储在磁盘上,如果有大量的索引,索引文件就可能比数据文件更快达到最大文件尺寸。
  3. 虽然索引大大提高了查询速度,同时却会降低更新表的速度。当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度

2、InnoDB中索引的推演

2.1索引之前的查找

先看一个精确匹配的例子:

select [列名列表] from 表名 where 列名 = xxx;

1、在一个页中的查找

假设目前表中的记录比较少,所有的记录都可以被存放在一个页中,在查找记录的时候可以根据搜索条件的不同分为两种情况:

  • 以主键为搜索条件

​ 可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。

  • 以其他列作为搜索条件

​ 因为在数据页中并没有对非主键列建立所谓的页目录,所以我们无法通过二分法快速定位相应的槽。这种情况下 只能从最小记录开始依次遍历单链表中的每条记录,然后对比每条记录是不是符合搜索条件。很显然,这种查找 的效率是非常低的。

2、在很多页中查找

大部分情况下我们表中存放的记录都是非常多的,需要好多的数据页来存储这些记录。在很多页中查找记录的话可以分为两个步骤:

1、定位到记录所在的页

2、从所在页内中查找相应的记录

在没有索引的情况下,不论是根据主键列或者其他列的值进行查找,由于我们并不能快速的定位到记录所在的页,所以只能从第一个页沿着双向链表一直往下找,在每一个页中根据我们上面的查找方式去查找指定的记录。因为要遍历所有的数据页,所以这种方式显然是超级耗时的。如果一个表有一亿条记录呢?此时索引应运而生。

2.2 设计索引

1、一个简单的索引设计方案

我们在根据某个搜索条件查找一些记录时为什么要遍历所有的数据页呢?因为各个页中的记录并没有规律,我们并不知道我们的搜索条件匹配哪些页中的记录,所以不得不依次遍历所有的数据页。所以如果我们想快速的定位到需要查找的记录在哪些数据页中该咋办?我们可以为快速定位记录所在的数据页而建立一个目录,建这个目录必须完成下边这些事:

下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。

假设:每个数据页最多能存放3条记录(实际上一个数据页非常大,可以存放下好多记录)。有了这个假设之后我们向index_demo表插入3条记录:

insert into index_demo values(1,4,'u'),(3,9,'d'),(5,3,'y');

那么这些基类已经按照主键值的大小串联成一个单向链表了,如图所以:

十分钟轻松掌握索引的数据结构_第1张图片

从图中可以看出来,index_demo表中的3条记录都被插入到了编号为10的数据页中了。此时我们再来插入—条记录:

insert into index_demo values(4,4,'a');

因为页1日最多只能放3条记录,所以我们不得不再分配一个新页:
十分钟轻松掌握索引的数据结构_第2张图片

注意,新分配的数据页编号可能并不是连续的。它们只是通过维护着上一个页和下一个页的编号而建立了链表关系。另外,页10中用户记录最大的主键值是5,而页28中有一条记录的主键值是4,因为5>4,所以这就不符合下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值的要求,所以在插入主键值为4的记录的时候需要伴随着一次记录移动,也就是把主键值为5的记录移动到页28中,然后再把主键值为4的记录插入到页10中,这个过程的示意图如下:

十分钟轻松掌握索引的数据结构_第3张图片

这个过程表明了在对页中的记录进行增删改操作的过程中,我们必须通过一些诸如记录移动的操作来始终保证这个状态一直成立:下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。这个过程我们称为页分裂。

给所有的页建立一个目录项

由于数据页的编号可能是不连续的,所以在向index_demo表中插入许多条记录后,可能是这样的效果:

十分钟轻松掌握索引的数据结构_第4张图片

因为这些16KB的页在物理存储上是不连续的,所以如果想从这么多页中根据主键值快速定位某些记录所在的页,我们需要给它们做一个目录,每个页对应一个目录项,每个目录项包括下边两个部分:

  • 页的用户记录中最小的主键值,我们用Key表示
  • 页号,我们用page_no表示。
    十分钟轻松掌握索引的数据结构_第5张图片

以页28为例,它对应目录项2,这个目录项中包含着该页的页号28以及该页中用户记录的最小主键值5。我们只需要把几个目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键值快速查找某条记录的功能了。比如:查找主键值为20的记录,具体查找过程分两步:
1.先从目录项中根据二分法快速确定出主键值为20的记录在目录项3中(因为 12<20 < 209),它对
应的页是页9。
2.再根据前边说的在页中查找记录的方式去页9中定位具体的记录。

2.3 常见索引概念

索引按照物理实现方式,索引可以分为2种:聚簇和非聚簇索引。我们也可以吧非聚簇索引称为二级索引或者辅助索引

1、聚簇索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式(所有的用户记录都存储在叶子结点),也就是所谓的索引即数据,数据即索引

术语“聚簇”表示数据行和相邻的键值聚簇的存储在一起

特点:

1、使用记录主键值的大小进行记录和页的顺序,这包括三个方面的含义:

  • 页内的记录是按照主键的大小顺序排成一个单向链表
  • 各个存放用户记录的页也是根据也中用户记录的主键大小顺序排成一个双向链表
  • 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表

2、B+树的叶子结点存储的是完整的用户记录。

所谓完整的用户记录,就是指这个基类中存储了所有列的值(包括隐藏列)。

我们把具有这两种特性的B+树称为聚簇索引,所有完整的用户记录都存放在这个聚簇索引的叶子节点处。这种聚簇索引并不需要我们在MySQL语句中显式的使用INDEX语句去创建,InnoDB存储引擎会自动的为我们创建聚簇索引。

优点:

  • 数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇缩阴哪种获取数据比非聚簇索引更快
  • 聚簇索引对于主键的排序查找和范围查找速度非常快
  • 按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以节省了大量的IO操作

缺点:

  • 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响 性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
  • 更新主键的代价很高,因此将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义为主键不可更新
  • 二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

限制:

  • 对于MySQL数据库目前只有InnoDB数据引擎支持聚簇索引,而MylSAM并不支持聚簇索引。
  • 由于数据物理存储排序方式只能有一种,所以每个MySQL的表只能有一个聚簇索引。一般情况下就是该表的主键。
  • 如果没有定义主键,Innodb会选择非空的唯一索引代替。如果没有这样的索引,Innodb会隐式的定义一个主键来作为聚簇索引。
  • 为了充分利用聚簇索引的聚簇的特性,所以innodb表的主键列尽量选用有序的顺序id,而不建议用无序的id,比如UUID、MD5、HASH、字符串列作为主键无法保证数据的顺序增长。

2、二级索引(辅助索引、非聚簇索引)

聚簇索引和非聚簇索引的区别:

  • 聚簇索引的叶子节点存储的就是我们的数据记录,非聚簇索引的叶子节点存储的是数据位置。非聚簇索引不会影响数据表的物理存储顺序。
  • 一个表只能有一个聚簇索引,因为只能有一种排序存储的方式,但可以有多个非聚簇索引,也就是多个索引目录提供数据检索。
  • 使用聚簇索引的时候,数据的查询效率高,但如果对数据进行插入,删除,更新等操作,效率会比非聚簇索引低。

3、联合索引

我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按照c2和c3列的大小进行排序,这个包含两层含义:

  • 先把各个记录和页按照c2列进行排序。
  • 在记录的c2列相同的情况下,采用c3列进行排序

2.4 InnoDB的B+索引的注意事项

  1. 根页面位置万年不动
  2. 内结点中目录项记录的唯一性
  3. 一个页面最少存储2条记录

3、MySQL数据结构选择的合理性

从MySQL的角度讲,不得不考虑一个现实问题就是磁盘IO。如果我们能让索引的数据结构尽量减少硬盘的Io操作,所消耗的时间也就越小。可以说,磁盘的I/0操作次数对索引的使用效率至关重要
查找都是索引操作,一般来说索引非常大,尤其是关系型数据库,当数据量比较大的时候,索引的大小有可能几个G甚至更多,为了减少索引在内存的占用,数据库索引是存储在外部磁盘上的。当我们利用索引查询的时候,不可能把整个索引全部加载到内存,只能逐一加载,那么MySQL衡量查询效率的标准就是磁盘Io次数。

3.1 Hash结构

Hash本身是一个函数,又被称为散列函数,它可以帮助我们大幅提升检索数据的效率。

Hash算法是通过某种确定性的算法(比如MD5、SHA1、SHA2、SHA3)将输入转变为输出。相同的输入永远可以得到相同的输出,假设输入内容有微小偏差,在输出中通常会有不同的结果。

加快查找速度的数据结构,常见的有两类:

  1. 树,例如平衡二叉搜索树,查询/插入/修改/删除的平均时间复杂度都是O(log2n);
  2. 哈希,例如HashMap,查询/插入/修改/删除的平均时间复杂度都是O(1);

采用Hash进行检索效率非常高,基本上一次检索就可以找到数据,而B+树需要自顶向下依次查找,多次访问节点才能找到数据,中间需要多次I/O操作,以从效率来说 Hash 比 B+树更快。

Hash结构效率高,那为什么索引结构要设计成树型呢?

原因1: Hash索引仅能满足(=)(<>)和IN查询。如果进行范围查询,哈希型的索引,时间复杂度会退化为O(n);而树型的“有序"”特性,依然能够保持O(log2N)的高效率。

原因2:Hash索引还有一个缺陷,数据的存储是没有顺序的,在ORDER BY的情况下,使用Hash索引还需要对数据重新排序。

原因3∶对于联合索引的情况,Hash值是将联合索引键合并后一起来计算的,无法对单独的一个键或者几个索引键进行查询。

原因4∶对于等值查询来说,通常Hash索引的效率更高,不过也存在一种情况,就是索引列的重复值如果很多,效率就会降低。这是因为遇到Hash冲突时,需要遍历桶中的行指针来进行比较,找到查询的关键字,非常耗时。所以,Hash索引通常不会用到重复值多的列上,比如列为性别、年龄的情况等。

3.2 二叉搜索树

如果我么利用二叉树作为索引结构,那么磁盘的IO次数和索引数的高度是相关的。

1、二叉搜索树的特点

  • 一个结点只能有两个子节点,也就是一个节点度不能超过2
  • 左节点 < 本结点;右结>=本节点

2、查找规则

我们先来看下最基础的二叉搜索树((Binary Search Tree),搜索某个节点和插入节点的规则一样,我们假设搜索插入的数值为key:

  1. 如果key大于根结点,则在右子树中进行查找
  2. 如果key小于根结点,则在左子树中进行查找
  3. 如果key等于根结点,也就是找到了这个结点,返回根结点即可。

十分钟轻松掌握索引的数据结构_第6张图片

当然也有极端情况,如下:
十分钟轻松掌握索引的数据结构_第7张图片

上面第二棵树也属于二分查找树,但是性能上已经退化成了一条链表,查找数据的时间复杂度变成了0(n)。你能看出来第一个树的深度是3,也就是说最多只需3次比较,就可以找到节点,而第二个树的深度是7,最多需要7次比较才能找到节点。
为了提高查询效率,就需要减少磁盘IO数。为了减少磁盘IO的次数,就需要尽量降低树的高度,需要把原来"瘦高"的树结构变的“矮胖”,树的每层的分叉越多越好。

3.3 AVL树

为了解决上面二叉查找树退化成链表的问题,人们提出了平衡二叉搜索树(Balanced Binary Tree),又称为AVL树(有别于AVL算法),它在二叉搜索树的基础上增加了约束,具有以下性质:

它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

这里说一下,常见的平衡二叉树有很多种,包括了平衡二叉搜索树、红黑树、数堆、伸展树。平衡二叉搜索树是最早提出来的自平衡二叉搜索树,当我们提到平衡二叉树时一般指的就是平衡二叉搜索树。事实上,第一棵树就属于平衡二叉搜索树,搜索时间复杂度就是0( log2n)。
数据查询的时间主要依赖于磁盘I/o的次数,如果我们采用二叉树的形式,即使通过平衡二叉搜索树进行了改进,树的深度也是o(log2n),当n比较大时,深度也是比较高的,比如下图的情况:
十分钟轻松掌握索引的数据结构_第8张图片

每访问一次节点就需要进行一次磁盘工/0操作,对于上面的树来说,我们需要进行5次I/O操作。虽然平衡二叉树的效率高,但是树的深度也同样高,这就意味着磁盘I/o操作次数多,会影响整体数据查询的效率。
针对同样的数据,如果我们把二叉树改成M叉树(M>2)呢?当M=3时,同样的1个节点可以由下面的三叉树来进行存储:
十分钟轻松掌握索引的数据结构_第9张图片

你能看到此时树的高度降低了,当数据量N大的时候,以及树的分叉数M大的时候,M叉树的高度会远小于二叉树的高度(M>2)。所以,我们需要把树从“瘦高"变“矮胖”

3.4 B-树

B树的英文是Balance Tree,也就是多路平衡查找树。简写为B-Tree(注意横杠表示这两个单词连接起来的意思,不是减号)。它的高度远远小于平衡二叉树

B树的结构如下图所示:

十分钟轻松掌握索引的数据结构_第10张图片

B树作为多路平衡查找树,它的每一个节点最多可以包括M个子节点,`M称为B树的阶|。每个磁盘块中包括了关键字和子节点的指针。如果一个磁盘块中包括了x个关键字,那么指针数就是x+1。对于一个100阶的B树来说,如果有3层的话最多可以存储约100万的索引数据。对于大量的索引数据来说,采用B树的结构是非常适合的,因为树的高度要远小于二叉树的高度。

小结:

  1. B树在插入和删除节点的时候如果导致树不平衡,就通过自动调整节点的位置来保持数的自平衡
  2. 关键字集合分布在整棵树中,即叶子结点和非叶子结点都存数据。搜索有可能在非叶子节点结束。
  3. 其搜索性能等价于在关键字全集中做一次二分查找。

3.5 B+Tree

B+树也是一种多路搜索树,基于B树做出了改进,主流的DBMS都支持B+树的索引方式,比如MySQL。相比B-Tree,B+Tree适合文件索引系统。

B+树的中间结点并不直接存储数据,B+树的查询效率更高,在查询范围上,B+树的效率比B树高。

过自动调整节点的位置来保持数的自平衡
2. 关键字集合分布在整棵树中,即叶子结点和非叶子结点都存数据。搜索有可能在非叶子节点结束。
3. 其搜索性能等价于在关键字全集中做一次二分查找。

3.5 B+Tree

B+树也是一种多路搜索树,基于B树做出了改进,主流的DBMS都支持B+树的索引方式,比如MySQL。相比B-Tree,B+Tree适合文件索引系统。

B+树的中间结点并不直接存储数据,B+树的查询效率更高,在查询范围上,B+树的效率比B树高。

你可能感兴趣的:(数据结构,b树,mysql,数据库)