目录
MySQL索引使用的数据结构主要有BTree索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
MySQL的BTree索引使用的是B树中的B+Tree,但对于主要的两种存储引擎的实现方式是不同的:
- MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
- InnoDB: 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
InnoDB数据结构
各个数据页可以组成一个双向链表
而每个数据页中的记录又可以组成一个单向链表
- 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录
- 以其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录。
所以说,如果我们写select * from user where id = '8'这样没有进行任何优化的sql语句,默认会这样做:
- 定位到记录所在的页,需要遍历双向链表,找到所在的页
- 从所在的页内中查找相应的记录,由于不是根据主键查询,只能遍历所在页的单链表了
哈希索引
除了B+树之外,还有一种常见的是哈希索引。
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。
本质上就是把键值换算成新的哈希值,根据这个哈希值来定位。
看起来哈希索引很强,能快速找到对应的项,但其实哈希索引有好几个局限(根据他本质的原理可得):
- 哈希索引也没办法利用索引完成排序
- 不支持最左匹配原则
- 在有大量重复键值情况下,哈希索引的效率也是极低的---->哈希碰撞问题。
- 不支持范围查询
最左匹配原则
最左匹配原则:
- 索引可以简单如一个列(a),也可以复杂如多个列(a, b, c, d),即联合索引。我们知道索引字段的数据必须是有序的,才能实现这种类型的查找,才能利用到索引。而在联合索引中,因为有多个字段,所以怎么排序索引就有讲究,它会按照联合索引 key 的顺序进行排序。例如(col1, col2),可能的数据内容如下。
- col1| col2
- 0 | 4
- 0 | 9
- 1 | 1
- 1 | 3
- 在使用上述联合索引时,索引只能用于查找key是否存在(相等),遇到范围查询(>、<、between、like左匹配)等就不能进一步匹配了,后续退化为线性查找。为什么呢?如果我们查询 col1 >= 0 && col1 <= 1 的话,col2 的索引部分就已经不是有序的了,我们没办法在无序的数据上进行二分查找。所以当出现范围查询时,后续的索引就会失效。
- 再来说说最左匹配的查询过程,当我们要查询 col1 = 1 & col2 = 1 时,可以先根据 col1 的索引找到所有 col1 = 1 的数据,然后在根据 col2 的索引找到所有 col2=1 的数据。这都没有问题。
- 但是,当我们需要直接查询 col2 = 1 时,上述索引就没法使用了,因为纵观所有 col2 字段的所有索引值的话,你会发现它实际上是无序的。因此,搜索时条件列的排列顺序决定了可命中索引的列数。
- 当我们进行条件查询时,mysql 会按照查询条件的顺序,找到左前缀相匹配的索引使用,值得一提的是,如果我们的查询条件是 col2 = 1 & col1 = 1,优化器会为我们优化为 col1 = 1 & col2 = 1,这样就能使用上述索引。