一起学习Mysql索引一(索引的数据结构)

相信大家在使用Mysql的时候,为了提高查询效率或多或少的会使用到索引。然而,在建立索引的时候,你是根据什么去创建索引对应的列的呢:每一个where查询条件建立一条索引?根据查询的列建立联合索引?还是在多列索引中将选择性最高的列放在第一列来建立索引?

如果我们不能够对Mysql索引有更深的了解,可能就只能凭借上面的一些经验法则去建立索引,而结果可能并不能达到预期的效果。

那么就和强哥一起学习Mysql索引,来提升自己对Mysql索引的理解吧!

索引的本质

索引(Index)是帮助MySQL高效获取数据的数据结构,而在不同索引的数据结构之上,使用不同的查找算法,在不同的场景下便能产生不同的查询效率。我们建立Mysql索引时,最长见的数据结构就是Hash和BTree。

Hash index

哈希索引基于哈希表实现。对于每一行数据,存储引擎都会对所有的索引列计算一个hash code,不同键值的行计算出来的hash code也不一样。哈希索引将所有的hash code存储在索引中,同时在哈希表中保存指向每个数据行的指针。Mysql中的Memory引擎支持非唯一的哈希索引,这种情况下,其实现原理类似Java中HashMap的底层实现:如果多个列的hash code值相同,索引会以链表的方式存放多个记录指针到同一个哈希条目中,先找到相同的hash code所在的链表后,在具体的比较找到最终的数据行。

因为索引自身存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找的速度非常快。然而,哈希索引也有他的限制:

  • 哈希索引数据不是按照索引顺序存储的,所以也就无法用于排序。

  • 哈希索引不支持部分索引匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。

  • 哈希索引只支持等值比较查询,包括=、IN()、<=>。

    扫描二维码关注公众号,回复: 8706365 查看本文章

由于哈希索引的限制较多,所以我们一般建立索引的时候很少使用哈希索引,强哥这里也就不再进行更详细的说明,有兴趣的同学可以自己去查询相关的知识。

B-Tree和B+Tree

目前大部分数据库系统及文件系统都采用B-Tree或其变种B+Tree作为索引结构。

B-Tree

首先定义一条数据记录为一个二元组[key, data],key为记录的键值,对于不同数据记录,key是互不相同的;data为数据记录除key外的数据。那么B-Tree是满足下列条件的数据结构:

  • d为大于1的一个正整数,称为B-Tree的度。也就是说,可以建立d=2或者d=3,d=4等等不同度的B-Tree。

  • h为一个正整数,称为B-Tree的高度。

  • 每个非叶子节点由n-1个key和n个指针组成,其中d<=n<=2d。

  • 每个叶子节点最少包含一个key和两个指针,最多包含2d-1个key和2d个指针,叶节点的指针均为null 。

  • 所有叶节点具有相同的深度,等于树高h。

  • key和指针互相间隔,节点两端是指针。

  • 一个节点中的key从左到右非递减排列。

  • 所有节点组成树结构。

  • 每个指针要么为null,要么指向另外一个节点。

由于B-Tree的特性,在B-Tree中按key检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到null指针,前者查找成功,后者查找失败。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, key);
    }
    return BTree_Search(point[i+1]->node, key);
}
data = BTree_Search(root, my_key);

一个度为d的B-Tree,设其索引N个key,则其树高度h的上限为logd((N+1)/2),检索一个key,其查找节点个数的渐进复杂度为O(logdN)。

B+Tree

MySQL就普遍使用B+Tree实现其索引结构。与B-Tree相比,B+Tree有以下不同点:

  • 每个节点的指针上限为2d而不是2d+1。

  • 内节点不存储data,只存储key;叶子节点不存储指针。

一般在数据库系统或文件系统中使用的B+Tree结构都在经典B+Tree的基础上进行了优化,增加了顺序访问指针。

在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能,例如上图中如果要查询key为从18到49的所有数据记录,当找到18后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。

为什么使用B+Tree作为索引呢?

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

根据B-Tree的定义,可知检索一次最多需要访问h个节点。数据库系统的设计者巧妙利用了磁盘预读原理(即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存),将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧:

每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。

B-Tree中一次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小(通常不超过3)。综上所述,用B-Tree作为索引结构效率是非常高的。

而B+Tree为什么比B-Tree更适合作为索引呢?原因与内节点初度d有关。从上面分析可以看到,d越大索引的性能越好,而出度的上限取决于节点内key和data的大小:

dmax = floor(pagesize / (keysize + datasize + pointsize))dmax = floor(pagesize / (keysize + datasize + pointsize))

floor表示向下取整。由于B+Tree内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。

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

Mysql索引实现

在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的,接下来我们主要讨论MyISAM和InnoDB两个存储引擎的索引实现方式。

MyISAM索引实现

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

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

同样也是一颗B+Tree,data域保存数据记录的地址。因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。MyISAM的索引方式也叫做“非聚集”的,之所以这么称呼是为了与InnoDB的聚集索引区分。

InnoDB索引实现

虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。

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

可以看到叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。

第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域:

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

BTree索引查询类型

如果我们创建了一个多列的联合索引,如key(last_name,first_name,birthday),则数据在索引树中的条目顺序就会按照的索引的列顺序进行排序。假如last_name相同的两个条目,会继续按照first_name进行排序。

BTree的联合索引顺序原理对于建立索引也有一定的指导意义,建立索引时,可以使用BTree索引的查询类型。BTree索引适用于全键值、键值范围或最左前缀查找。

  • 全值匹配:索引中的所有列覆盖所有的查询列所进行的匹配。

  • 最左前缀匹配:即按从左往右的优先级进行匹配,如果遇到某列是范围匹配,之后的列将不再进行匹配。例如,查询所有的姓为王,并且名字是强开头的人,即第一列last_name全匹配,第二列first_name范围匹配。

  • 匹配列前缀:即只匹配某一列的值得开头部分。例如可用于查找所有以王开头的姓的人。这里只使用了索引的第一列。

  • 匹配范围值:例如索引可以查找姓为王年或陈的人。这里只使用了索引的第一列。

  • 只访问索引的查询:即查询只需要访问索引,而无需访问数据行。这也就是我们说的“覆盖索引”查询,效率极高。

因为索引树种的节点顺序是有序的,所以除了按值查找之外,索引还可以用于查询中的Order By操作。一般来说,如果BTree可以按照某种方式查找到值,那么也可以按照这种方式用于排序。所以,如果Order By子句满足前面列出的几种查询类型,则这个索引也可以满足对应的排序需求。

总结

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

在下一篇推文中,强哥将向大家介绍Mysql索引有关的高性能策略,了解了上面的内容,我们才能更好的去了解Mysql索引创建及优化的原理。

本文参考:

《高性能Mysql第三版》

http://blog.codinglabs.org/articles/theory-of-mysql-index.html

关注公众号获取更多内容,有问题也可在公众号提问哦:

强哥叨逼叨

叨逼叨编程、互联网的见解和新鲜事

发布了54 篇原创文章 · 获赞 69 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/seanxwq/article/details/93120560