摘要:

本篇介绍下Mysql的InnoDB索引相关知识,从各种树到索引原理到存储的细节。

InnoDB是Mysql的默认存储引擎(Mysql5.5.5之前是MyISAM,文档)。本着高效学习的目的,本篇以介绍InnoDB为主。少量涉及MyISAM作为对照。

这篇文章是我在学习过程中总结完毕的。内容主要来自书本和博客(參考文献会给出)。过程中增加了一些自己的理解。描写叙述不准确的地方烦请指出。

1 各种树形结构

本来不打算从二叉搜索树開始,由于网上已经有太多相关文章,可是考虑到清晰的图示对理解问题有非常大帮助。也为了保证文章完整性,最后还是加上了这部分。

先看看几种树形结构:

1 搜索二叉树:每一个节点有两个子节点。数据量的增大必定导致高度的高速添加。显然这个不适合作为大量数据存储的基础结构。

2 B树:一棵m阶B树是一棵平衡的m路搜索树。

最重要的性质是每一个非根节点所包括的keyword个数 j 满足:┌m/2┐ - 1 <= j <= m - 1;一个节点的子节点数量会比keyword个数多1,这样keyword就变成了子节点的切割标志。

通常会在图示中把keyword画到子节点中间。很形象,也easy和后面的B+树区分。因为数据同一时候存在于叶子节点和非叶子结点中,无法简单完毕按顺序遍历B树中的keyword,必须用中序遍历的方法。

3 B+树:一棵m阶B树是一棵平衡的m路搜索树。

最重要的性质是每一个非根节点所包括的keyword个数 j 满足:┌m/2┐ - 1 <= j <= m;子树的个数最多能够与keyword一样多。

非叶节点存储的是子树里最小的keyword。同一时候数据节点仅仅存在于叶子节点中。且叶子节点间添加了横向的指针,这样顺序遍历全部数据将变得很easy。

4 B*树:一棵m阶B树是一棵平衡的m路搜索树。最重要的两个性质是1每一个非根节点所包括的keyword个数 j 满足:┌m2/3┐ - 1 <= j <= m;2非叶节点间加入了横向指针。

B/B+/B*三种树有相似的操作,比方检索/插入/删除节点。这里仅仅重点关注插入节点的情况,且仅仅分析他们在当前节点已满情况下的插入操作。由于这个动作略微复杂且能充分体现几种树的差异。

与之对照的是检索节点比較easy实现,而删除节点仅仅要完毕与插入相反的过程就可以(在实际应用中删除并非插入的全然逆操作。往往仅仅删除数据而保留下空间为兴许使用)。

先看B树的分裂,下图的红色值即为每次新插入的节点。

每当一个节点满后,就须要发生分裂(分裂是一个递归过程,參考以下7的插入导致了两层分裂)。因为B树的非叶子节点相同保存了键值。所以已满节点分裂后的值将分布在三个地方:1原节点,2原节点的父节点。3原节点的新建兄弟节点(參考5。7的插入过程)。

分裂有可能导致树的高度添加(參考3。7的插入过程)。也可能不影响树的高度(參考5。6的插入过程)。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdm9pZGNjYw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

B+树的分裂:当一个结点满时。分配一个新的结点,并将原结点中1/2的数据拷贝到新结点。最后在父结点中添加新结点的指针;B+树的分裂仅仅影响原结点和父结点,而不会影响兄弟结点。所以它不须要指向兄弟节点的指针。

B*树的分裂:当一个结点满时,假设它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中。再在原结点插入keyword,最后改动父结点中兄弟结点的keyword(由于兄弟结点的keyword范围改变了)。假设兄弟也满了。则在原结点与兄弟结点之间添加新结点。并各复制1/3的数据到新结点。最后在父结点添加新结点的指针。

能够看到B*树的分裂很巧妙。由于B*树要保证分裂后的节点还要2/3满,假设採用B+树的方法,仅仅是简单的将已满的节点一分为二,会导致每一个节点仅仅有1/2满,这不满足B*树的要求了。所以B*树採取的策略是在本节点满后。继续插入兄弟节点(这也是为什么B*树须要在非叶子节点加一个兄弟间的链表),直到把兄弟节点也塞满,然后拉上兄弟节点一起凑份子,自己和兄弟节点各出资1/3成立新节点,这种结果是3个节点刚好是2/3满,达到B*树的要求,皆大欢喜。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdm9pZGNjYw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

B+树适合作为数据库的基础结构。全然是由于计算机的内存-机械硬盘两层存储结构。内存能够完毕高速的随机訪问(随机訪问即给出随意一个地址,要求返回这个地址存储的数据)可是容量较小。而硬盘的随机訪问要经过机械动作(1磁头移动 2盘片转动),訪问效率比内存低几个数量级,可是硬盘容量较大。典型的数据库容量大大超过可用内存大小,这就决定了在B+树中检索一条数据非常可能要借助几次磁盘IO操作来完毕。

例如以下图所看到的:通常向下读取一个节点的动作可能会是一次磁盘IO操作,只是非叶节点一般会在初始阶段加载内存以加快訪问速度。同一时候为提高在节点间横向遍历速度。真实数据库中可能会将图中蓝色的CPU计算/内存读取优化成二叉搜索树(InnoDB中的page
directory机制)。



真实数据库中的B+树应该是很扁平的,能够通过向表中顺序插入足够数据的方式来验证InnoDB中的B+树究竟有多扁平。我们通过例如以下图的CREATE语句建立一个仅仅有简单字段的測试表,然后不断加入数据来填充这个表。通过下图的统计数据(来源见參考文献1)能够分析出几个直观的结论,这几个结论宏观的展现了数据库里B+树的尺度。

1 每一个叶子节点存储了468行数据,每一个非叶子节点存储了大约1200个键值,这是一棵平衡的1200路搜索树!

2 对于一个22.1G容量的表。也仅仅须要高度为3的B+树就能存储了,这个容量大概能满足非常多应用的须要了。假设把高度增大到4。则B+树的存储容量立马增大到25.9T之巨!

3 对于一个22.1G容量的表,B+树的高度是3。如果要把非叶节点所有载入到内存也仅仅须要少于18.8M的内存(怎样得出的这个结论?由于对于高度为2的树,1203个叶子节点也仅仅须要18.8M空间,而22.1G从良表的高度是3,非叶节点1204个。

同一时候我们如果叶子节点的尺寸是大于非叶节点的,由于叶子节点存储了行数据而非叶节点仅仅有键和少量数据。

)。仅仅使用如此少的内存就能够保证仅仅须要一次磁盘IO操作就检索出所需的数据,效率是很之高的。


2 Mysql的存储引擎和索引

能够说数据库必须有索引,没有索引则检索过程变成了顺序查找,O(n)的时间复杂度差点儿是不能忍受的。我们很easy想象出一个仅仅有单keyword组成的表怎样使用B+树进行索引,仅仅要将keyword存储到树的节点就可以。当数据库一条记录里包括多个字段时,一棵B+树就仅仅能存储主键。假设检索的是非主键字段,则主键索引失去作用,又变成顺序查找了。

这时应该在第二个要检索的列上建立第二套索引。

这个索引由独立的B+树来组织。

有两种常见的方法能够解决多个B+树訪问同一套表数据的问题。一种叫做聚簇索引(clustered index ),一种叫做非聚簇索引(secondary
index)。

这两个名字尽管都叫做索引。但这并非一种单独的索引类型。而是一种数据存储方式。对于聚簇索引存储来说,行数据和主键B+树存储在一起。辅助键B+树仅仅存储辅助键和主键。主键和非主键B+树差点儿是两种类型的树。

对于非聚簇索引存储来说。主键B+树在叶子节点存储指向真正数据行的指针,而非主键。

InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id = 14"这种条件查找主键,则依照B+树的检索算法就可以查找到相应的叶节点,之后获得行数据。

若对Name列进行条件搜索。则须要两个步骤:第一步在辅助索引B+树中检索Name。到达其叶子节点获取相应的主键。第二步使用主键在主索引B+树种再运行一次B+树检索操作,终于到达叶子节点就可以获取整行数据。

MyISM使用的是非聚簇索引,非聚簇索引的两棵B+树看上去没什么不同。节点的结构全然一致仅仅是存储的内容不同而已。主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有不论什么区别。因为索引树是独立的。通过辅助键检索无需訪问主键的索引树。

为了更形象说明这两种索引的差别。我们假想一个表例如以下图存储了4行数据。当中Id作为主索引。Name作为辅助索引。图示清晰的显示了聚簇索引和非聚簇索引的差异。

我们重点关注聚簇索引,看上去聚簇索引的效率明显要低于非聚簇索引。由于每次使用辅助索引检索都要经过两次B+树查找,这不是多此一举吗?聚簇索引的优势在哪?

1 因为行数据和叶子节点存储在一起,这样主键和行数据是一起被加载内存的,找到叶子节点就能够立马将行数据返回了。假设依照主键Id来组织数据,获得数据更快。

2 辅助索引使用主键作为"指针" 而不是使用地址值作为指针的优点是,降低了当出现行移动或者数据页分裂时辅助索引的维护工作,使用主键值当作指针会让辅助索引占用很多其它的空间,换来的优点是InnoDB在移动行时无须更新辅助索引中的这个"指针"。

也就是说行的位置(实现中通过16K的Page来定位,后面会涉及)会随着数据库里数据的改动而发生变化(前面的B+树节点分裂以及Page的分裂),使用聚簇索引就能够保证无论这个主键B+树的节点怎样变化,辅助索引树都不受影响。

3 Page结构

假设说前面的内容偏向于解释原理,那后面就開始涉及详细实现了。

理解InnoDB的实现不得不提Page结构,Page是整个InnoDB存储的最基本构件,也是InnoDB磁盘管理的最小单位。与数据库相关的全部内容都存储在这样的Page结构里。

Page分为几种类型,常见的页类型有数据页(B-tree Node)Undo页(Undo Log Page)系统页(System Page) 事务数据页(Transaction System Page)等。单个Page的大小是16K(编译宏UNIV_PAGE_SIZE控制),每一个Page使用一个32位的int值来唯一标识,这也正好相应InnoDB最大64TB的存储容量(16Kib
* 2^32 = 64Tib)。一个Page的基本结构例如以下图所看到的:

每一个Page都有通用的头和尾。可是中部的内容依据Page的类型不同而发生变化。Page的头部里有我们关心的一些数据,下图把Page的头部具体信息显示出来:

我们重点关注和数据组织结构相关的字段:Page的头部保存了两个指针,分别指向前一个Page和后一个Page,头部还有Page的类型信息和用来唯一标识Page的编号。依据这两个指针我们非常easy想象出Page链接起来就是一个双向链表的结构。

再看看Page的主体内容。我们主要关注行数据和索引的存储,他们都位于Page的User Records部分,User Records占领Page的大部分空间,User Records由一条一条的Record组成,每条记录代表索引树上的一个节点(非叶子节点和叶子节点)。

在一个Page内部。单链表的头尾由固定内容的两条记录来表示。字符串形式的"Infimum"代表开头,"Supremum"代表结尾。

这两个用来代表开头结尾的Record存储在System Records的段里。这个System
Records和User Records是两个平行的段。InnoDB存在4种不同的Record,它们各自是1主键索引树非叶节点 2主键索引树叶子节点 3辅助键索引树非叶节点 4辅助键索引树叶子节点。

这4种节点的Record格式有一些差异,可是它们都存储着Next指针指向下一个Record。兴许我们会具体介绍这4种节点,如今仅仅须要把Record当成一个存储了数据同一时候含有Next指针的单链表节点就可以。

User Record在Page内以单链表的形式存在。最初数据是依照插入的先后顺序排列的,可是随着新数据的插入和旧数据的删除,数据物理顺序会变得混乱。但他们依旧保持着逻辑上的先后顺序。

把User Record的组织形式和若干Page组合起来,就看到了略微完整的形式。

如今看下怎样定位一个Record:

1 通过根节点開始遍历一个索引的B+树,通过各层非叶子节点终于到达一个Page,这个Page里存放的都是叶子节点。

2 在Page内从"Infimum"节点開始遍历单链表(这样的遍历往往会被优化),假设找到该键则成功返回。假设记录到达了"supremum"。说明当前Page里没有合适的键。这时要借助Page的Next Page指针,跳转到下一个Page继续从"Infimum"開始逐个查找。

具体看下不同类型的Record里究竟存储了什么数据,依据B+树节点的不同,User Record能够被分成四种格式。下图种依照颜色予以区分。

1 主索引树非叶节点(绿色)

1 子节点存储的主键里最小的值(Min Cluster Key on Child),这是B+树必须的,作用是在一个Page里定位到详细的记录的位置。

2 最小的值所在的Page的编号(Child Page Number),作用是定位Record。

2 主索引树叶子节点(黄色)

1 主键(Cluster Key Fields),B+树必须的。也是数据行的一部分

2 除去主键以外的全部列(Non-Key Fields)。这是数据行的除去主键的其它全部列的集合。

这里的1和2两部分加起来就是一个完整的数据行。

3 辅助索引树非叶节点非(蓝色)

1 子节点里存储的辅助键值里的最小的值(Min Secondary-Key on Child)。这是B+树必须的,作用是在一个Page里定位到详细的记录的位置。

2 主键值(Cluster Key Fields)。非叶子节点为什么要存储主键呢?由于辅助索引是能够不唯一的,可是B+树要求键的值必须唯一。所以这里把辅助键的值和主键的值合并起来作为在B+树中的真正键值。保证了唯一性。可是这也导致在辅助索引B+树中非叶节点反而比叶子节点多了4个字节。(即下图中蓝色节点反而比红色多了4字节)

3 最小的值所在的Page的编号(Child Page Number),作用是定位Record。

4 辅助索引树叶子节点(红色)

1 辅助索引键值(Secondary Key Fields),这是B+树必须的。

2 主键值(Cluster Key Fields),用来在主索引树里再做一次B+树检索来找到整条记录。

以下是本篇最重要的部分了,结合B+树的结构和前面介绍的4种Record的内容,我们最终能够画出一幅全景图。因为辅助索引的B+树与主键索引有相似的结构,这里仅仅画出了主键索引树的结构图,仅仅包括了"主键非叶节点"和"主键叶子节点"两种节点。也就是上图的的绿色和黄色的部分。

把上图还原成以下这个更简洁的树形示意图,这就是B+树的一部分。注意Page和B+树节点之间并没有一一相应的关系。Page仅仅是作为一个Record的保存容器,它存在的目的是便于对磁盘空间进行批量管理,上图中的编号为47的Page在树形结构上就被拆分成了两个独立节点。

至此本篇就算结束了。本篇仅仅是对InnoDB索引相关的数据结构和实现进行了一些梳理总结,并未涉及到Mysql的实战经验。这主要是基于几点原因:

1 原理是基石,仅仅有充分了解InnoDB索引的工作方式,我们才有能力高效的使用好它。

2 原理性知识特别适合使用图示,我个人很喜欢这样的表达方式。

3 关于InnoDB优化,在《高性能Mysql》里有更加全面的介绍,对优化Mysql感兴趣的同学全然能够自己获取相关知识,我自己的积累还未达到能分享这些内容的地步。

另:对InnoDB实现有很多其它兴趣的同学能够看看Jeremy Cole的博客(參考文献三篇文章的来源)。这位老兄曾先后在Mysql。Yahoo,Twitter。Google从事数据库相关工作。他的文章很棒!

參考文献:

[1] Jeremy Cole The physical structure of InnoDB index pages

[2] Jeremy Cole B+Tree index structures in InnoDB

[3] Jeremy Cole The physical structure of records in InnoDB

[4] 姜承尧  MySQL技术内幕-InnoDB存储引擎 第二版

[5] Schwartz,B / Zaitsev,P / Tkach 高性能Mysql 第三版

[6] B-tree wiki

剖析Mysql的InnoDB索引的更多相关文章

  1. MySQL之InnoDB索引面试学习笔记

    写在前面 想要做好后台开发,终究是绕不过索引这一关的.先问自己一个问题,InnoDB为什么选择B+树作为默认索引结构.本文主要参考MySQL索引背后的数据结构及算法原理和剖析Mysql的InnoDB索 ...

  2. MySQL的InnoDB索引原理详解

    摘要 本篇介绍下Mysql的InnoDB索引相关知识,从各种树到索引原理到存储的细节. InnoDB是Mysql的默认存储引擎(Mysql5.5.5之前是MyISAM,文档).本着高效学习的目的,本篇 ...

  3. MySQL的InnoDB索引原理详解 (转)

    摘要: 本篇介绍下Mysql的InnoDB索引相关知识,从各种树到索引原理到存储的细节. InnoDB是Mysql的默认存储引擎(Mysql5.5.5之前是MyISAM,文档).本着高效学习的目的,本 ...

  4. mysql在innodb索引下b+树的高度问题。

    B+树索引介绍 B+树索引的本质是B+树在数据库中的实现.但是B+树索引有一个特点是高扇出性,因此在数据库中,B+树的高度一般在2到3层.也就是说查找某一键值的记录,最多只需要2到3次IO开销.按磁盘 ...

  5. MySQL系列(九)--InnoDB索引原理

    InnoDB在MySQL5.6版本后作为默认存储引擎,也是我们大部分场景要使用的,而InnoDB索引通过B+树实现,叫做B-tree索引.我们默认创建的 索引就是B-tree索引,所以理解B-tree ...

  6. 一分钟掌握MySQL的InnoDB引擎B+树索引

    MySQL的InnoDB索引结构采用B+树,B+树什么概念呢,二叉树大家都知道,我们都清楚随着叶子结点的不断增加,二叉树的高度不断增加,查找某一个节点耗时就会增加,性能就会不断降低,B+树就是解决这个 ...

  7. 0614MySQL的InnoDB索引原理详解

    转自http://www.cnblogs.com/shijingxiang/articles/4743324.html MySQL的InnoDB索引原理详解 http://www.admin10000 ...

  8. mysql InnoDB 索引小记

    0.索引结构 1).MyISAM与InnoDB索引结构比较,如下: 2).MyISAM的索引结构 主键索引和二级索引结构很像,叶子存储的都是索引以及数据存储的物理地址,其他节点存储的仅仅是索引信息.其 ...

  9. 【原创】MySQL(Innodb)索引的原理

    引言 回想四年前,我在学习mysql的索引这块的时候,老师在讲索引的时候,是像下面这么说的 索引就像一本书的目录.而当用户通过索引查找数据时,就好比用户通过目录查询某章节的某个知识点.这样就帮助用户有 ...

随机推荐

  1. python利用有道翻译实现“语言翻译器”的功能

    import urllib.request import urllib.parse import json while True: content = input('请输入需要翻译的内容(退出输入Q) ...

  2. SQLServer2008 关于Having

    转自百度百科 与where 相比 HAVING 子句对 GROUP BY 子句设置条件的方式与 WHERE 子句和 SELECT 语句交互的方式类似. WHERE 子句搜索条件在进行分组操作之前应用: ...

  3. 《CSS Mastery》 读书笔记 (1)

    --本笔记中英混合,专业名词尽量不翻译,免得误解,如果不习惯,就不用往下看了,走好不送. 第一章 基础 人类的好奇心总是促使我们捣鼓,捣鼓是最好做有效的学习CSS的方法 In this chapter ...

  4. 快速录入快递地址API接口实现

    电商.ERP等行业下单环节极其重要,如何提高下单的效率已经成为首要问题.快速下单对于客户来说,为提前发货争取了时间:对于卖家来说,提高了库存周转率及利用率.快速下单的接口实现,需要解决如下几个问题:1 ...

  5. Ubuntu中在终端进入root权限但是总提示密码错误的解决方案

    先解除root锁定,为root用户重新设置密码 打开终端输入:sudo passwd(注意是passwd不是password) Password: <--- 输入你当前用户的密码 Enter n ...

  6. nvcc fatal : Unsupported gpu architecture 'compute_11'

    使用VS编译OpenCV编译源代码时候,对Cmake生成的工程文件编译,会出现 nvcc fatal : Unsupported gpu architecture 'compute_11'  问题.原 ...

  7. Windows下VS2013 C++编译测试faster-rcnn

    [原创帖!转载请注明出处:http://www.cnblogs.com/LaplaceAkuir/p/6445189.html] 本人最近研究faster-rcnn,在ubuntu成功跑通matlab ...

  8. 【汇编】MASM6.15几个简单的汇编程序

    /***************通过调用(INT 21H)表中的01h号功能号从键盘输入一个字符并回显到视频显示器上*****************/ DATAS SEGMENT ;此处输入数据段代 ...

  9. HTML5 video常用属性

    一.视频video常用标签方法 <!-- controls 控制条,播放暂停等 controlslist 控制不允许全屏 不允许下载等 poster 封面 autoplay 自动播放 muted ...

  10. .NET Framework 3.5 安装

    今天vCenter服务器悲剧了,只好火速重新部署新vCenter服务器... Windows server 2016 中,安装VCenter 5.5 提示  未安装 .NET Framework 3. ...