B+树索引

索引的目的在于提高查询效率,可以类比字典,如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql。如果没有索引,那么你可能需要把所有单词看一遍才能找到你想要的,如果我想找到m开头的单词呢?或者ze开头的单词呢?是不是觉得如果没有索引,这个事情根本无法完成?

我们都知道CPU是很快的,磁盘是很慢的,要想提高数据库的访问效率,可以说非常大的一个优化点就是减少磁盘IO访问。每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,B+树应运而生。B+树索引的本质就是B+树在数据库中的实现,但是B+树索引在数据库中有一个特点是高扇出性,因此在数据库中,B+树的高度一般都在2-4层,这也就是说查找某一键值得行记录最多只需要2-4次IO。这倒不错,因为当前一般的机械磁盘每秒至少可以做100次IO,2-4次的IO意味着查询时间只需要0.02-0.04秒。

数据库中的B+树索引可以分为聚集索引(clustered index)和辅助索引(secondary index),但是不管是聚集索引还是辅助的索引,其内部组都是B+树的,即高度平衡的,叶子节点存放着所有的数据。聚集索引与辅助索引不同的是,叶子节点存放的是否是一整行的信息。

聚集索引(聚簇索引)

InnoDB存储引擎表是索引组织表,即表中数据按照主键顺序存放。而聚簇索引就是按照每张表的主键构造一棵B+树,同时叶子节点中存放的即为整张表的行记录数据,也将聚集索引的叶子节点称为数据页。聚簇索引的这个特性决定了索引组织表中数据也是索引的一部分。同B+树数据结构一样,每个数据页都通过一个双向链表来进行链接。由于实际的数据页只能按照一棵B+树进行排序,因此每张表只能拥有一个聚集索引。在多数情况下,查询优化器倾向于采用聚集索引。因为聚集索引能够在B+树索引的叶子节点上直接找到数据。此外,由于定义了数据的逻辑顺序,聚集索引能够特别快地访问针对范围值的查询。查询优化器能够快速发现某一段范围的数据页需要扫描。

许多文档会告诉我们:聚集索引按照顺序物理地存储数据,但是试想一下,如果聚集索引必须按照特定顺序存放物理记录,则维护成本显得非常之高。所以,聚集索引的存储并不是物理上连续的,而是逻辑上连续的。这其中有两点:一是前面说过的页通过双向链表链接,页按照主键的顺序排序;另一点是每个页中的记录也是通过双向链表进行维护的,物理存储上可以同样不按照主键存储。

聚集索引的另一个好处是,它对于主键的排序查找和范围查找速度非常快。叶子节点的数据就是用户所要查询的数据。如用户需要查询一张注册用户的表,查询最后注册的10位用户,由于B+树索引是双向链表的,用户可以快速找到最后一个数据页。并取出10条记录。若用命令EXPLAIN进行分析,可得:

mysql> explain select * from info order by id limit 10\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: info
type: index
possible_keys: NULL
key: PRIMARY
key_len: 4
ref: NULL
rows: 10
Extra:
1 row in set (0.00 sec)mysql> explain select * from info order by id limit 10\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: info
type: index
possible_keys: NULL
key: PRIMARY
key_len: 4
ref: NULL
rows: 10
Extra:
1 row in set (0.00 sec)

可以看到虽然使用ORDER BY对记录进行排序,但是在实际过程中并没有进行所谓的filesort操作,而这就是因为聚集索引的特点。

另一个是范围查询,即如果要查找主键某一范围内的数据,通过叶子节点的上层中间节点就可以得到页的范围,之后直接读取数据页即可。又如:

mysql> EXPLAIN select * from profile where id>10 and id<1000\G
id: 1
select_type: SIMPLE
table: profile
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: NULL
rows: 14868
Extra: Using where
1 row in set (0.00 sec)mysql> EXPLAIN select * from profile where id>10 and id<1000\G
id: 1
select_type: SIMPLE
table: profile
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: NULL
rows: 14868
Extra: Using where
1 row in set (0.00 sec)

执行EXPLAIN得到了MySQL数据库的执行计划,并且在rows列中给出了一个查询结果的预估返回行数。要注意的是,rows代表的是一个预估值,不是确切的值。

如下图,是一颗B+树(聚簇索引):

关于b+树的定义可以参见B+树,这里只说一些重点,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3。P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。

b+树性质

通过上面的分析,我们知道IO次数取决于b+数的高度h。假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小。而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。

当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的搜索方向,如果name相同再依次比较age和sex,最后得到检索的数据。但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。

我们在查询数据时,一般都会在经常被查询的字段上面建立一个索引(B+树索引),这正是利用了索引中被排序的键值,通过内节点的索引功能及叶子节点中数据的有序性(索引默认是升序的),利用二分查找极大的提高了查找的性能,所以索引在数据库中的作用是至关重要的。

b+树的查找过程

如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。

辅助索引

对于辅助索引(secondary index,也称二级索引),叶子节点并不包含行记录的全部数据,叶子节点除了包含键值以外,每个叶子节点中的索引行中还包含了一个书签。该书签用来告诉Innodb存储引擎哪里可以找到与索引相对应的行数据。由于Innodb存储引擎是索引组织表,因此Innodb存储引擎的辅助索引的书签就是相应行数据的聚集索引键。

辅助索引的存在并不影响数据在聚集索引中的组织,因此每张表上可以有多个辅助索引。当通过辅助索引来寻找数据时,Innodb存储引擎会遍历辅助索引并通过叶级别的指针获得指向主键索引的主键,然后再通过主键索引来找到一个完整的行记录。举例来说,如果在一棵高度为3的辅助索引树种查找数据,那需要对这棵辅助索引树遍历3次找到指定主键,如果聚集索引树的高度同样为3,那么还需要对聚集索引树进行3次查找,最终找到一个完整的行数据所在的页。因此一共需要6次逻辑IO访问以得到最终的一个数据页。

从上面的特性我们可以知道,一个表中,聚簇索引占用的空间肯定是最大的,因为它是存储了全部数据的,而辅助索引,是建立在某几个需要经常查询的列上面的,除了这几个列之外,剩下的就是用来“回表”的指针信息了,所以相对而言,辅助索引的占用空间都会比聚簇索引小很多,特别是在一个表的列数很多或是这些列中包含大字段的情况下,因为我们一般都不会在大字段上直接建立索引。那这样比较下来,在我们统计一个表总的精确行数时(查COUNT*),一些优化器就会选择表中最小的索引来作为统计的目标索引,因为它占用空间最小,IO也会最小,性能相应的更快一些。

上面说到了“回表”,所谓回表,就是在使用辅助索引时,因为辅助索引只存储了部分数据,如果根据键值查找到的数据不能包括全部目标数据时,就需要通过二级索引的指针,也就是键值对中的值,来找到聚簇索引的全部数据,然后根据完整的数据取出所需要的列的过程。这种在二级索引中不能找到所有需要的数据列的现象,被称为非覆盖索引,反之称为覆盖索引。因为回表本身是需要去另一个索引(聚簇索引)中查找数据的,性能必然会受到影响,那为了尽可能的提高性能就需要尽量的减少回表次数,所以可以试着将出现频率非常高的语句中所有使用到的列以合适的顺序建一个二级索引,这样所有需要的列都被这个二级索引覆盖了,就不需要回表了,从而一定程度上提高了性能。这虽然是一个好的做法,但需要去权衡,因为需要考虑语句中涉及到的列数,这个语句出现的频率及最终这个索引的大小。最坏的情况是建一个和聚簇索引差不多大的二级索引,这样一方面是占用空间比较大,另一方面是维护这个二级索引对这个表的整体修改性能也是有影响的,所以各方面都需要去权衡,然后再决定是不是要这样做。

上面还说到了,在统计总行数的时候,可以直接使用二级索引来做,是因为有一个很明显但很重要的前提:每个二级索引与聚簇索引的总行数是一样的,并且一对一。只不过在每一个索引中,数据行的排序顺序不同,可以想象二级索引行与聚簇索引行行之间都有虚线相连,并且二级索引中每一行都有且只有一条虚线指向聚簇索引中的一行数据,而聚簇索引的每一行,都会有相同个数的虚线指进来,这个数目就是二级索引的个数。至于二级索引与聚簇索引究竟是如何连起来的,我们后面会详细讲述。

辅助索引的指针

现在已经知道,聚簇索引存储了所有数据,二级索引只存储了部分数据,但二级索引是为了提高性能的,所以经常会被使用到,那如果二级索引中的数据不能满足需求怎么办?这就用到了我们上面提到的“回表”,也就是二级索引中每行记录中指针的作用。

关于聚簇索引及二级索引列之间的逻辑关系,我们分类如下:

自定义主键的聚簇索引:
索引结构:[主键列][TRXID][ROLLPTR][其它建表创建的非主键列]

参与记录比较的列:主键列

内结点KEY列:[主键列]+PageNo指针

未定义主键的聚簇索引:
索引结构:[ROWID][TRXID][ROLLPTR][其它建表创建的非主键列]

参与记录比较的列:只ROWID一列而已

内结点KEY列:[ROWID]+PageNo指针

自定义主键的二级唯一索引:
索引结构:[唯一索引列][主键列]

参与记录比较的列:[唯一索引列][主键列]

内结点KEY列:[唯一索引列]+PageNo指针

自定义主键的二级非唯一索引:
索引结构:[非唯一索引列][主键列]

参与记录比较的列:[非唯一索引列][主键列]

内结点KEY列:[非唯一索引列][主键列]+PageNo指针

未定义主键的二级唯一索引:
索引结构:[唯一索引列][ROWID]

参与记录比较的列:[唯一索引列][ROWID]

内结点KEY列:[唯一索引列]+PageNo指针

未定义主键的二级非唯一索引:
索引结构:[非唯一索引列][ROWID]

参与记录比较的列:[非唯一索引列][ROWID]

内结点KEY列:[非唯一索引列][ROWID]+PageNo指针

通过这六种情况,讲清楚了聚簇索引记录包含的列,二级索引记录包括的列,以及在非叶子节点中分别包含的列,因为索引是用来检索数据的,所以还讲述了用来检查记录时,在二级索引及聚簇索引中,参与比较记录大小的列分别是什么,唯一索引与非唯一索引的区别等。

需要注意的一点是,上面讲述的索引列的顺序关系,与实际索引中记录的物理存储不是一回事,记录的存储格式是记录的格式,而这个是索引在内存中是元组的组织关系,这个元组的顺序体现的就是每个索引自己的逻辑顺序,以什么列建的索引,什么列就会在最前面起到优先排序的作用。

我们这里特别关注一下二级唯一索引的元组逻辑顺序,二级唯一索引中,作为索引本身的索引列,就是我们上面所说的“键”,当这个元组需要回表时,在元组中存储的聚簇索引列信息,就是我们所说的“值”,这样就形成了键值对。而对于二级非唯一索引而言,因为只有索引列本身再加上主键列才能保证索引记录是唯一的,所以这二者合起来才能构成我们所说的“键”,而“值”就为空了,也就是说,二级非唯一索引中,在记录构成方面,非叶结节点只是比叶子节点多了一个PageNo指针信息。

从上面可以看到,二级索引元组中,首先存储的就是每个索引定义的索引列,接着就是这条记录对应的聚簇索引的主键列的值,而主键列是唯一的,所以二级索引回表时对应的记录也是唯一的,这样就形成了一种指针的效果。

不过有一点需要注意一下,二级索引回表时对应的聚簇索引,如果是用户自定义的,有可能是自增列,也有可能是有逻辑意义的单列或者组合列的聚簇索引,如果用户没有自定义,则InnoDB会自动给聚簇索引分配一个主键列,不过是隐藏的列,即我们所熟知的Rowid列。基于此,如果是用户自定义的聚簇索引,则二级索引指针指向的就是聚簇索引所包含的列,如果没有自定义主键,那该指针就指向Rowid列了。

转自http://www.ywnds.com/?p=9976

MySQL InnoDB存储引擎 聚集和非聚集索引相关推荐

  1. MySQL Innodb存储引擎使用B+树做索引的优点

    对于数据库来说,索引和表数据都是存放在磁盘上的,一般使用B+树作为索引 MySQL Innodb存储引擎使用了B+树作为索引的优点,主要有以下原因: 1.索引和表数据都是存放在磁盘上的,如果磁盘上的数 ...

  2. MySQL InnoDB存储引擎

    呵呵哒... MySQL体系结构和存储引擎 首先要搞懂的是什么是数据库,什么是数据库实例. 数据库:物理操作系统文件或其他形式文件类型的集合. 实例:MySQL数据库由后台线程以及一个共享内存区组成, ...

  3. MySQL InnoDB 存储引擎索引那些事儿

    InnoDB 存储引擎中,表是根据主键顺序组织存放的,称为索引组织表.每个表都有一个主键,如果没有显示定义主键,则会选择第一个创建的非空唯一索引作为主键,如果没有非空唯一索引,InnoDB引擎则自动创 ...

  4. 为什么MySQL InnoDB 存储引擎要用B+树做索引,而不用B树?

    为什么MySQL InnoDB 存储引擎 要用B+树做索引,而不用B树? (1)B+树空间利用率更高,可减少I/O次数 一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存 ...

  5. 浅析Mysql InnoDB存储引擎事务原理

    浅析Mysql InnoDB存储引擎事务原理 大神:http://blog.csdn.net/tangkund3218/article/details/47904021

  6. 数据结构与索引-- mysql InnoDB存储引擎索引

    索引与算法 索引是我们在应用开发过程中程序数据可开发的一个重要助力.也是一个重要的研究方向,索引太多,应用的性能可能受到影响,如果索引太少,对查询性能又会有制约.我们需要找到一个合适的平衡点,这个对性 ...

  7. mysql InnoDB存储引擎的介绍

    mysql InnoDB存储引擎的介绍 概念 1.InnoDB是MySQL默认的存储引擎,如果需要其不支持的特性,则考虑使用其他存储发动机. 2.InnoDB采用MVCC支持高并发,实现四个标准隔离级 ...

  8. 谈谈MySQL InnoDB存储引擎事务的ACID特性

    2019独角兽企业重金招聘Python工程师标准>>> 1.前言 狼哥一直对数据库Mysql这块没有进行过系统的深入学习,今天看到一篇文章写的还不错,特意分享一下,我不能保证文章中所 ...

  9. mysql innodb 存储引擎

    --MySQL 结构有两部分组成 1.MySQL server 层 2.存储引擎层 --注:到 存储引擎层之前都属于 MySQL server 层 MySQL 5.1到 5.7 ,大版本 没有变化 , ...

最新文章

  1. thymeleaf引用图片_SpringBoot访问静态资源(图片、html)以及整合thymeleaf 注意点
  2. GNU parted的指令說明[转]
  3. sql随机查询数据语句(NewID(),Rnd,Rand(),random())
  4. python numpy安装-Python--Numpy安装
  5. 3.3 神经网络的输出-深度学习-Stanford吴恩达教授
  6. 云炬随笔20211012(3)
  7. UIAutomation 自动化
  8. 酱油和gbt酱油哪个好_韩国酱油真的这么好,到底怎么挑?
  9. BizTalk Server 2010新功能介绍(六):BizTalk Mapper (下)
  10. linux 定时执行搅拌,Linux上定时shell脚本
  11. 盘点最重要的7个Python库
  12. python图像增强_【Tool】Augmentor和imgaug——python图像数据增强库
  13. geojson 河流_GeoJSON 数据类型 | JShare
  14. SQL进阶六:字符串函数
  15. Android中如何实现多个框,在android中的对话框中设置多个文本框
  16. android学习笔记_Intent
  17. linux用户态定时器,一种基于linux用户态调用定时器的方法及系统的制作方法
  18. 校园招聘的简历写作与面试技巧
  19. 年三十 放鞭炮 驱年兽 迎新春 财运到 兔年大吉
  20. 四种做动态数据可视化的方法

热门文章

  1. MySQL插入语句优化
  2. PLC与单片机有什么不同?
  3. 世博上黑莓手机啦 英文版本
  4. 天大2021年秋学期考试《土力学与基础工程》离线作业考核试题
  5. css 居中代码 加粗代码,css居中代码是什么
  6. Android编写一个视频监控App
  7. 计算机科学投稿初审被增刊,科学网—投稿过程中的困惑:增刊 - 张晓锋的博文...
  8. 2020年5月总结(网络拥塞控制和增强学习初瞰)
  9. Android 接入微信扫码库,实现堪比微信的扫码效果
  10. ecmall时间的问题