首页 > 技术文章 > 数据库索引原理和innodb特点

feng-zhi 2021-06-13 11:36 原文

1.数据库索引:它是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。

但是为表设置索引也是要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。

上图展示了一种可能的索引方式,左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在O(log2n)的复杂度内获取到相应数据。

创建索引可以大大提高系统的性能。
优点:

第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。

第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。 

也许会有人要问:增加索引有如此多的优点,为什么不对表中的每一个列创建一个索引呢?因为,增加索引也有许多不利的方面。
缺点:

第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。

第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。

第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

2.什么是B树及其变种B+树?

想要理解索引原理必须清楚一种数据结构「平衡树」(非二叉),也就是b tree或者 b+ tree,重要的事情说三遍:“平衡树,平衡树,平衡树”。当然, 有的数据库也使用哈希桶作用索引的数据结构 , 然而, 主流的RDBMS都是把平衡树当做数据表默认的索引数据结构的。

1)B树

B树中每个节点包含了键值和键值对于的数据对象存放地址指针,所以成功搜索一个对象可以不用到达树的叶节点。
成功搜索包括节点内搜索和沿某一路径的搜索,成功搜索时间取决于关键码所在的层次以及节点内关键码的数量。

在B树中查找给定关键字的方法是:首先把根结点取来,在根结点所包含的关键字K1,…,kj查找给定的关键字(可用顺序查找或二分查找法),若找到等于给定值的关键字,则查找成功;否则,一定可以确定要查的关键字在某个Ki或Ki+1之间,于是取Pi所指的下一层索引节点块继续查找,直到找到,或指针Pi为空时查找失败。

2)B+树

B+树非叶节点中存放的关键码并不指示数据对象的地址指针,非叶节点只是索引部分。所有的叶节点在同一层上,包含了全部关键码和相应数据对象的存放地址指针,且叶节点按关键码从小到大顺序链接。如果实际数据对象按加入的顺序存储而不是按关键码次数存储的话,叶节点的索引必须是稠密索引,若实际数据存储按关键码次序存放的话,叶节点索引时稀疏索引。
B+树有2个头指针,一个是树的根节点,一个是最小关键码的叶节点。

所以 B+树有两种搜索方法:

一种是按叶节点自己拉起的链表顺序搜索。

一种是从根节点开始搜索,和B树类似,不过如果非叶节点的关键码等于给定值,搜索并不停止,而是继续沿右指针,一直查到叶节点上的关键码。所以无论搜索是否成功,都将走完树的所有层。

B+ 树中,数据对象的插入和删除仅在叶节点上进行。

这两种处理索引的数据结构的不同之处:

a,B树中同一键值不会出现多次,并且它有可能出现在叶结点,也有可能出现在非叶结点中。而B+树的键一定会出现在叶结点中,并且有可能在非叶结点中也有可能重复出现,以维持B+树的平衡。
b,因为B树键位置不定,且在整个树结构中只出现一次,虽然可以节省存储空间,但使得在插入、删除操作复杂度明显增加。B+树相比来说是一种较好的折中。
c,B树的查询效率与键在树中的位置有关,最大时间复杂度与B+树相同(在叶结点的时候),最小时间复杂度为1(在根结点的时候)。而B+树的时候复杂度对某建成的树是固定的。

我们平时建表的时候都会为表加上主键, 在某些关系数据库中, 如果建表时不指定主键,数据库会拒绝建表的语句执行。事实上, 一个加了主键的表,并不能被称之为「表」。一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐, 跟我认知中的「表」很接近。如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是上面说的「平衡树」结构,换句话说,就是整个表就变成了一个索引。没错, 再说一遍, 整个表变成了一个索引,也就是所谓的「聚集索引」
这就是为什么一个表只能有一个主键, 一个表只能有一个「聚集索引」,因为主键的作用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置。

假如一张表有一亿条数据 ,需要查找其中某一条数据,按照常规逻辑, 一条一条的去匹配的话, 最坏的情况下需要匹配一亿次才能得到结果,用大O标记法就是O(n)最坏时间复杂度,这是无法接受的,而且这一亿条数据显然不能一次性读入内存供程序使用, 因此, 这一亿次匹配在不经缓存优化的情况下就是一亿次IO开销,以现在磁盘的IO能力和CPU的运算能力, 有可能需要几个月才能得出结果 。
如果把这张表转换成平衡树结构(一棵非常茂盛和节点非常多的树),假设这棵树有10层,那么只需要10次IO开销就能查找到所需要的数据, 速度以指数级别提升,用大O标记法就是O(log n),n是记录总树,底数是树的分叉数,结果就是树的层次数。换言之,查找次数是以树的分叉数为底,记录总数的对数,用公式来表示就是

我们假设记录总数为一亿,树的分叉树为10,那么查找次数就位O(log10 100000000),查找次数为9,这里的结果从亿降到了个位数。因此,利用索引会使数据库查询有惊人的性能提升。

然而, 事物都是有两面的, 然而, 事物都是有两面的, 索引能让数据库查询数据的速度上升, 而使写入数据的速度下降,原因很简单的, 因为平衡树这个结构必须一直维持在一个正确的状态, 增删改数据都会改变平衡树各节点中的索引数据内容,破坏树结构, 因此,在每次数据改变时, DBMS必须去重新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销,也就是为什么索引会给查询以外的操作带来副作用的原因。, 因此,在每次数据改变时, DBMS必须去重新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销,也就是为什么索引会给查询以外的操作带来副作用的原因。

3为什么使用B/B+Tree不使用红黑树结构?

B-Tree中渐进复杂度为O(h)=O(logdN)。一般实际应用中,出度d(树的分叉树)是非常大的数字,通常超过100,N为记录总数,因此h非常小(通常不超过3)。

而红黑树这种结构,h明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的I/O渐进复杂度也为O(h),效率明显比B-Tree差很多。

综上所述,用B-Tree作为索引结构效率是非常高的。

4.innodb四大特性

4.1.插入缓冲(Insert Buffer/Change Buffer),适用对象:

  • 非唯一索引
  • 非聚集索引

为什么要有这个特性,或者说这个特性解决了什么问题?

Innodb数据表本身就是一个聚集索引表,表中的记录按照聚集索引顺序存储,插入时按聚集索引自增插入,顺序写磁盘,速度是有保证的;
但对于非聚集索引,插入操作就不那么顺利了,非聚集索引并非按顺序插入,因此在插入非聚集索引叶节点时,为随机插入,性能不高;

聚集索引:
索引的顺序与实际的顺序相同
比如根据拼音查字典

非聚集索引
索引的顺序与实际的顺序不同
比如根据部首查字典

具体操作

如果索引页在缓冲池中,则直接插入
如果不在,则先放入一个插入缓冲区中,返回插入成功的结果。
master thread会定时将插入缓冲区中的数据插入数据库
此举将多个插入合并到一个IO操作中,从而提高了性能

4.2.双写机制(Double Write)

先了解:部分写失效

当数据库正在从内存向磁盘写一个数据页时,数据库宕机,从而导致这个页只写了部分数据,就是部分写失效。

为什么要有这个特性,或者说这个特性解决了什么问题?

部分写失效会导致数据丢失。是无法通过重做日志恢复的,因为重做日志记录的是对页的物理修改,如果页本身已经损坏,重做日志也无能为力。
具体操作如图:

1.先将数据写入缓冲区
2.分两次将缓冲区的数据写入磁盘共享表空间,每次写入1MB
3.讲缓冲区的数据写入数据文件
假如宕机
4.将共享表空间的的页覆盖原有数据页
5.再应用重做日志

两次写性能开销只在第二步
由于磁盘共享表空间是连续的,因此开销不是很大
可通过skip_innodb_doublewrite属性禁用,默认开启

4.3自适应哈希索引(Adaptive Hash Index,AHI)

InnoDB存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引
InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。
InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立hash索引。加快索引读取的效果,相当于索引的索引,帮助InnoDB快速读取索引页。

注意:hash查询是等值查询,例如模糊查询、范围查找,是不能使用hash索引的。用户可以根据实际场景去权衡是否要开启AHI。

4.4.预读 (Read Ahead)

预读(read-ahead)操作是一种IO操作,用于异步将磁盘的页读取到buffer pool中,预料这些页会马上被读取到。预读请求的所有页集中在一个范围内。InnoDB使用两种预读算法:
a.Linear read-ahead:线性预读技术预测在buffer pool中被访问到的数据它临近的页也会很快被访问到。能够通过调整被连续访问的页的数量来控制InnoDB的预读操作,使用参数 innodb_read_ahead_threshold配置,添加这个参数前,InnoDB会在读取到当前区段最后一页时才会发起异步预读请求。

innodb_read_ahead_threshold 这个参数控制InnoDB在检测顺序页面访问模式时的灵敏度。如果在一个区块顺序读取的页数大于或者等于 innodb_read_ahead_threshold 这个参数,InnoDB启动预读操作来读取下一个区块。innodb_read_ahead_threshold参数值的范围是 0-64,默认值为56. 这个值越高则访问默认越严格。比如,如果设置为48,在当前区块中当有48个页被顺序访问时,InnoDB就会启动异步的预读操作,如果设置为8,则仅仅有8个页被顺序访问就会启动异步预读操作。你可以在MySQL配置文件中设置这个值,或者通过SET GLOBAL 语句动态修改(需要有set global 权限)。
b. Random read-ahead:随机预读通过buffer pool中存中的来预测哪些页可能很快会被访问,而不考虑这些页的读取顺序。如果发现buffer pool中存中一个区段的13个连续的页,InnoDB会异步发起预读请求这个区段剩余的页。通过设置 innodb_random_read_ahead 为 ON开启随机预读特性。

通过 SHOW INNODB ENGINE STATUS 命令输出的统计信息可以帮助你评估预读算法的效果,统计信息包含了下面几个值:

  • nnodb_buffer_pool_read_ahead 通过预读异步读取到buffer pool的页数
  • innodb_buffer_pool_read_ahead_evicted 预读的页没被使用就被驱逐出buffer pool的页数,这个值与上面预读的页数的比值可以反应出预读算法的优劣。
  • innodb_buffer_pool_read_ahead_rnd 由InnoDB触发的随机预读次数。

此次分享,到此结束!

推荐阅读