一 序
本篇继续整理Innodb索引实现原理。
二 B+树
B+树属于索引的基础,不在详细介绍插入删除过程。只介绍特点。
1 搜索二叉树:每个节点有两个子节点,数据量的增大必然导致高度的快速增加,显然这个不适合作为大量数据存储的基础结构。
2 B树(m阶):一棵m阶B树是一棵平衡的m路搜索树。
每个节点之多拥有m棵子树;
根结点至少拥有两颗子树(存在子树的情况下);
除了根结点以外,其余每个分支结点至少拥有 m/2 棵子树;
所有的叶结点都在同一层上;
有 k 棵子树的分支结点则存在 k-1 个关键码,关键码按照递增次序进行排列;
关键字数量需要满足ceil(m/2)-1 <= n <= m-1;
特点:
关键字集合分布在整颗树中;
任何一个关键字出现且只出现在一个节点中;
每个节点存储data和key;
搜索有可能在非叶子节点结束;
一个节点中的key从左到右非递减排列;
所有叶节点具有相同的深度,等于树高h。
3 .B+树:
根结点只有一个,分支数量范围为[2,m]
分支结点,每个结点包含分支数范围为[ceil(m/2), m];
分支结点的关键字数量等于其子分支的数量减一,关键字的数量范围为[ceil(m/2)-1, m-1],关键字顺序递增;
所有叶子结点都在同一层;
特点:
所有关键字都存储在叶子节上,且链表中的关键字是有序的;
不可能非叶子节点命中返回;
非叶子节点相当于叶子节点的索引,叶子节点相当于是存储(关键字)数据的数据层 带顺序访问指针的B+树提高了区间查找能力
B+树与B树区别:
B+非叶子节点不存储data,只存储key
所有的关键字全部存储在叶子节点上
每个叶子节点含有一个指向相邻叶子节点的指针
非叶子节点可以看成索引部分,节点中仅含有其子树(根节点)中的最大(或最小)关键字
三 索引的设计
数据库作为存取数据的工具,对应性能影响主要有三块:CPU,内存,磁盘 。
索引是一冲存储形式,影响最大的就是磁盘。索引查找过程要产生磁盘I/O,相对于内存存取,磁盘I/O要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是查找过程中磁盘I/O操作次数的渐进复杂度。磁盘原理很多书都介绍了,简单说一下:而硬盘的随机访问要经过机械动作(1磁头移动 2盘片转动),访问效率比内存低几个数量级,但是硬盘容量较大。典型的数据库容量大大超过可用内存大小,这就决定了在B+树中检索一条数据很可能要借助几次磁盘IO操作来完成。如下图所示:通常向下读取一个节点的动作可能会是一次磁盘IO操作,不过非叶节点通常会在初始阶段载入内存以加快访问速度。同时为提高在节点间横向遍历速度,真实数据库中可能会将图中蓝色的CPU计算/内存读取优化成二叉搜索树(InnoDB中的page directory机制)。
结合关系型数据库的特点:行存储,每行有主键,可以形成键值对,键值可以排序。
先说下一开始的平衡二叉树与磁盘预读:
我们知道磁盘的存取速度比主存的慢很多,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。而平衡二叉树的深度H高,由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以平衡二叉树的I/O渐进复杂度也为O(h),效率明显比B树差很多。
再看为啥B+树比B树适合做索引:B+内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
但这不是最主要的因素,关键是B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。梁斌老师对此解释很好:B+树还有一个最大的好处,方便扫库,B树必须用中序遍历的方法按序扫库,而B+树直接从叶子结点挨个扫一遍就完了,B+树支持range-query非常方便,而B树不支持。这是数据库选用B+树的最主要原因。
另外B树也好B+树也好,根或者上面几层因为被反复query,所以这几块基本都在内存中,不会出现读磁盘IO,一般已启动的时候,就会主动换入内存。
真实数据库中的B+树应该是非常扁平的,可以通过向表中顺序插入足够数据的方式来验证InnoDB中的B+树到底有多扁平。通常是单表在千万级,大小几十个G的情况下,高度是3.高度是4的情况通常实际业务达不到已经分表了。
到此,索引理论结束了,可以有个雏形了,跟之前的Innodb的文件管理串起来。
三 聚簇索引与二级索引
在查询数据时,通常在被查询列建一个索引,这是利用了索引中被排序的键值。通过内节点的索引功能及叶子节点的有序性,利用二分查找的方法,极大提高了查询性能。
每个InnoDB的表都拥有一个特殊索引,此索引中存储着行记录(称之为聚簇索引Clustered Index),一般来说,聚簇索引是根据主键生成的。聚簇索引按照如下规则创建:
当定义了主键后,InnoDB会利用主键来生成其聚簇索引;
如果没有主键,InnoDB会选择一个非空的唯一索引来创建聚簇索引;
如果这也没有,InnoDB会隐式的创建一个自增的列(rowid)来作为聚簇索引。
除了主键索引之外的索引,成为二级索引(Secondary Index)。二级索引可以有多个,二级索引建立在经常查询的列上。与聚簇索引的区别在于二级索引的叶子节点中存放的是除了这几个列外用来回表的主键信息(指针)。
所为回表:就是在使用二级索引时,因为二级索引只存储了部分数据,如果根据键值查找的数据不能包含全部目标数据,就需要根据二级索引的键值的主键信息,去聚簇索引的全部数据。然后根据完整数据取出所需要的列。这种在二级索引不能找到全部列的现象称为“非索引覆盖”,需要两次B+树查询,反之称为索引覆盖。所以索引需要平衡考虑,多建索引有利于查询,但是占用空间大还影响写入性能。即索引要精有用。
为啥要这样设计呢?
1 由于行数据和叶子节点存储在一起,这样主键和行数据是一起被载入内存的,找到叶子节点就可以立刻将行数据返回了,如果按照主键Id来组织数据,获得数据更快。
2 辅助索引使用主键作为"指针" 而不是使用地址值作为指针的好处是,减少了当出现行移动或者数据页分裂时辅助索引的维护工作,使用主键值当作指针会让辅助索引占用更多的空间,换来的好处是InnoDB在移动行时无须更新辅助索引中的这个"指针"。也就是说行的位置(实现中通过16K的Page来定位,后面会涉及)会随着数据库里数据的修改而发生变化(前面的B+树节点分裂以及Page的分裂),使用聚簇索引就可以保证不管这个主键B+树的节点如何变化,辅助索引树都不受影响。
在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的
MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址,不是本文重点不展开。
InnoDB也使用B+