干货:mysql索引的数据结构

索引

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。node

咱们知道,数据库查询是数据库的最主要功能之一。咱们都但愿查询数据的速度能尽量的快,所以数据库系统的设计者会从查询算法的角度进行优化。最基本的查询算法固然是顺序查找(linear search),这种复杂度为O(n)的算法在数据量很大时显然是糟糕的,好在计算机科学的发展提供了不少更优秀的查找算法,例如二分查找(binary search)、二叉树查找(binary tree search)等。若是稍微分析一下会发现,每种查找算法都只能应用于特定的数据结构之上,例如二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,可是数据自己的组织结构不可能彻底知足各类数据结构(例如,理论上不可能同时将两列都按顺序进行组织),因此,在数据以外,数据库系统还维护着知足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就能够在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
看一个例子:mysql

图1.png

图1展现了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并非必定物理相邻的)。为了加快Col2的查找,能够维护一个右边所示的二叉查找树,每一个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就能够运用二叉查找在O(log2n)的复杂度内获取到相应数据。
虽然这是一个货真价实的索引,可是实际的数据库系统几乎没有使用二叉查找树或其进化品种 红黑树(red-black tree)实现的,缘由会在下文介绍。

B-Tree和B+Tree

目前大部分数据库系统及文件系统都采用B-Tree或其变种B+Tree做为索引结构,在本文的下一节会结合存储器原理及计算机存取原理讨论为何B-Tree和B+Tree在被如此普遍用于索引,这一节先单纯从数据结构角度描述它们。算法

B-Tree

是一种多路搜索树(并非二叉的):
1.定义任意非叶子结点最多只有M个儿子;且M>2;
2.根结点的儿子数为[2, M];
3.除根结点之外的非叶子结点的儿子数为[M/2, M];
4.每一个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
5.非叶子结点的关键字个数=指向儿子的指针个数-1;
6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的
子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
8.全部叶子结点位于同一层;
9.每一个k对应一个data。
如:(M=3)至关于一个2–3树,2–3树是一个这样的一棵树, 它的每一个节点要么有2个孩子和1个数据元素,要么有3个孩子和2个数据元素,叶子节点没有孩子,而且有1个或2个数据元素。sql



B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,若是命中则结束,不然进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已是叶子结点;B-Tree上查找算法的伪代码以下:数据库

BTree_Search(node, key) { if(node == null) return null; foreach(node.key) { if(node.key[i] == key) return node.data[i]; if(node.key[i] > key) return BTree_Search(point[i]->node); } return BTree_Search(point[i+1]->node); } data = BTree_Search(root, my_key);数据结构

  • B-树的特性:
    1.关键字集合分布在整颗树中;
    2.任何一个关键字出现且只出如今一个结点中;
    3.搜索有可能在非叶子结点结束;
    4.其搜索性能等价于在关键字全集内作一次二分查找;
    5.自动层次控制;性能

  • B-树的自控制:
    B树中每个内部节点会包含必定数量的键值。一般,键值的数量被选定在d和2d之间。在实际中,键值占用了节点中大部分的空间。因数2将保证节点能够被拆分或组合。若是一个内部节点有2d个键值,那么添加一个键值给此节点的过程,将会拆分2d键值为2个d键值的节点,并把此键值添加给父节点。每个拆分的节点须要最小数目的键值。类似地,若是一个内部节点和他的邻居二者都有d个键值,那么将经过它与邻居的合并来删除一个键值。删除此键值将致使此节点拥有d-1个键值;与邻居的合并则加上d个键值,再加上从邻居节点的父节点移来的一个键值。结果为彻底填充的2d个键值。优化

  • B-树的构造过程:
    下面是往B树中依次插入
    6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4ui


    btreebuild.gif

B+Tree

B-Tree有许多变种,其中最多见的是B+Tree,例如MySQL就广泛使用B+Tree实现其索引结构。设计

  • 与B-Tree相比,B+Tree有如下不一样点:
    1.非叶子结点的子树指针与关键字个数相同;
    2.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
    3.为全部叶子结点增长一个链指针;
    4.全部关键字都在叶子结点出现;
    5.内节点不存储data,只存储key
    如:(M=3)


    Paste_Image.png

B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树能够在非叶子结点命中),其性能也等价于在关键字全集作一次二分查找;

  • B+的特性:
    1.全部关键字都出如今叶子结点的链表中(稠密索引),且链表中的关键字刚好是有序的;
    2.不可能在非叶子结点命中;
    3.非叶子结点至关因而叶子结点的索引(稀疏索引),叶子结点至关因而存储(关键字)数据的数据层;
    4.更适合文件索引系统;

  • B+树的构造过程:
    下面是往B+树中依次插入
    6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4


    Bplustreebuild.gif

索引的物理存储

通常来讲,索引自己也很大,不可能所有存储在内存中,所以索引每每以索引文件的形式存储的磁盘上。这样的话,索引查找过程当中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,因此评价一个数据结构做为索引的优劣最重要的指标就是在查找过程当中磁盘I/O操做次数的渐进复杂度。换句话说,索引的结构组织要尽可能减小查找过程当中磁盘I/O的存取次数。

B-tree

Paste_Image.png

假如每一个盘块能够正好存放一个B树的结点(正好存放2个文件名)。那么一个BTNODE结点就表明一个盘块,而子树指针就是存放另一个盘块的地址。

  • 下面,我们来模拟下查找文件29的过程:
    1.根据根结点指针找到文件目录的根磁盘块1,将其中的信息导入内存。【磁盘IO操做 1次】
    2.此时内存中有两个文件名1七、35和三个存储其余磁盘页面地址的数据。根据算法咱们发现:17<29<35,所以咱们找到指针p2。
    3.根据p2指针,咱们定位到磁盘块3,并将其中的信息导入内存。【磁盘IO操做 2次】
    4.此时内存中有两个文件名26,30和三个存储其余磁盘页面地址的数据。根据算法咱们发现:26<29<30,所以咱们找到指针p2。
    5.根据p2指针,咱们定位到磁盘块8,并将其中的信息导入内存。【磁盘IO操做 3次】
    6.此时内存中有两个文件名28,29。根据算法咱们查找到文件名29,并定位了该文件内存的磁盘地址。
    分析上面的过程,发现须要3次磁盘IO操做和3次内存查找操做。关于内存中的文件名查找,因为是一个有序表结构,能够利用折半查找提升效率。至于IO操做是影响整个B树查找效率的决定因素。

固然,若是咱们使用平衡二叉树的磁盘存储结构来进行查找,磁盘4次,最多5次,并且文件越多,B树比平衡二叉树所用的磁盘IO操做次数将越少,效率也越高。

B+tree

Paste_Image.png
  • B+tree的优势:
  1. B+-tree的磁盘读写代价更低
    ****B+-tree****的内部结点并无指向关键字具体信息的指针。所以其内部结点相对B 树更小。若是把全部同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的须要查找的关键字也就越多。相对来讲IO读写次数也就下降了。
    举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点须要2个盘快。而****B+
    ****树内部结点只须要1个盘快。当须要把内部结点读入内存中的时候,B 树就比****B+ ****树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。
  2. B+-tree的查询效率更加稳定
    因为非终结点并非最终指向文件内容的结点,而只是叶子结点中关键字的索引。因此任何关键字的查找必须走一条从根结点到叶子结点的路。全部关键字查询的路径长度相同,致使每个数据的查询效率至关。

mysql的两种存储引擎的索引存储机制

MyISAM索引实现

MyISAM引擎使用B+Tree做为索引结构,叶节点的data域存放的是数据记录的地址。下图是MyISAM索引的原理图:


Paste_Image.png

这里设表一共有三列,假设咱们以Col1为主键,则上图是一个MyISAM表的主索引(Primary key)示意。能够看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是惟一的,而辅助索引的key能够重复。若是咱们在Col2上创建一个辅助索引,则此索引的结构以下图所示:


Paste_Image.png

一样也是一颗B+Tree,data域保存数据记录的地址。所以,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,若是指定的Key存在,则取出其data域的值,而后以data域的值为地址,读取相应数据记录。
MyISAM的索引方式也叫作“非汇集”的。

InnoDB索引实现

虽然InnoDB也使用B+Tree做为索引结构,但具体实现方式却与MyISAM大相径庭。

第一个重大区别是InnoDB的数据文件自己就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件自己就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,所以InnoDB表数据文件自己就是主索引。


Paste_Image.png

上图是InnoDB主索引(同时也是数据文件)的示意图,能够看到叶节点包含了完整的数据记录。这种索引叫作汇集索引。由于InnoDB的数据文件自己要按主键汇集,因此InnoDB要求表必须有主键(MyISAM能够没有),若是没有显式指定,则MySQL系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段做为主键,这个字段长度为6个字节,类型为长整形。

第二个与MyISAM索引的不一样是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的全部辅助索引都引用主键做为data域。例如,定义在Col3上的一个辅助索引:


Paste_Image.png

这里以英文字符的ASCII码做为比较准则。汇集索引这种实现方式使得按主键的搜索十分高效,可是辅助索引搜索须要检索两遍索引:首先检索辅助索引得到主键,而后用主键到主索引中检索得到记录。

了解不一样存储引擎的索引实现方式对于正确使用和优化索引都很是有帮助,例如知道了InnoDB的索引实现后,就很容易明白为何不建议使用过长的字段做为主键,由于全部辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再例如,用非单调的字段做为主键在InnoDB中不是个好主意,由于InnoDB数据文件自己是一颗B+Tree,非单调的主键会形成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段做为主键则是一个很好的选择。

相关文章
相关标签/搜索