一、详见key-value查找数据结构
基于key、value数据结构可分为hashmap、二叉树、二叉平衡树、红黑树、B树、B+树,下面粗略介绍各种数据结构的特点,从而分析为为什么MYSQL选择B+数作为索引的数据结构。
- HasMap:散列表,内部通过数组+链表实现,插入数据时通过计算 key的hash值与数组大小取模或者位运算,获得在数组中的下标,如果出现hash冲突就采用链表或者红黑树的方式解决。在散列非常均匀的情况下,通过精确key获取value很快,时间复杂度可达到O(1)。可以发现 hashmap读hash函数的要求很高,不然不出现散列不均匀,从而转化成链表或红黑树,导致查询时间复杂度降低为O(n)或者O(logN);并且它的数据不能持久化到磁盘,只能在内存中,如果数据量大的话,内存难以承载。还有一点它不支持高效的范围查找,只有通过精确值查找时速度很快,范围查找时,需要遍历比对。因此hashmap不适合做MYSQL的索引数据结构。
- 二叉查找树:二叉树是一个树形结构数据结构,节点的左子节点值总是小于自身值,右子节点总是大于自身值,可以发现如果插入一组有序数据,它会变得特别长,类似一个链表,这样会导致查询效率很低,因此二叉查找树不适合做MYSQL索引。
- 二叉平衡树:为了解决二叉查找树带来的问题,二叉平衡树在插入的树节点后,对树进行多次旋转,以让树达到左右平衡,它要求最短子树和最长子树之间的差值不能超过1。这个看着貌似可以解决不平衡的问题,但是它有一个问题就是插入效率太低,插入一个节点需要进行多次树的旋转,节点移动操作,如果业务中有大量的数据插操作,效率会很低。因此它也不适合做MYSQL索引的数据结构。
- 红黑树:为了解决插入效率低的问题,红黑树出现了,它不再要求树的高度之差为1,而是要求最长子树的高度不超多最短子树的2倍即可,并且把树的节点分为红色节点和黑色节点,规定任何一个路径里所有的黑色节点必须一致,一个路径中不能连续有两个红色节点。可以发现树的分叉太少,导致随着数据的增多树的高度之差会不断增大,查询效率会逐渐降低,如果能把树的分叉增多,高度之差就没那么大了。
- B树:为了解决树的高度之差和平衡性的问题,B数出现了,它不再限制树的分叉限制,一个节点可以有多个子节点,叶子结点从左到右是从大到小排列的,可以支持范围查找,并且一个树节点可以存储多条数据,允许将索引和一行data记录都放在一个树节点中,可以灵活设置一个节点包含的最大子节点数量,通常叫阶,用m表示,也可称为m叉树,一个节点中最大的元素数量为m-1,以此来控制树的高度,就是一个节点横向存储的数据越多,节点的高度就越低。由于节点中存储key,同时包含data,而每个节点的存储空间是有限的,如果data比较大的话,会导致每个节点中存储的key减少,从而会导致树的深度较大,进而影响查询性能。
- B+树:是对B树的一个优化,他在非叶子节点中只存储key,不存储data,叶子节点中存储key+data,从而在节点中存储更多的key,降低树的高度,并且将所有的树节点都按照顺序排列在叶子节点上,相邻叶子节点之间相互连接,头尾相接,形成一个环形的有序双向链表。
二、MYSQL中的B+树实现
MYSQL索引的数据结构采用B+tree实现,如下图所示,每个节点是一个磁盘块,非叶子节点存储key,叶子节点存储key和数据,这样非叶子节点中可以存储更多的key,一是降低树的高度,二是相邻key之间形成范围区间,这样可将一个大的数据范围划分为多个小的区间,区间越多,查找的效率就越高。叶子节点两两指针相互连接,这种结构符合磁盘的预读特性,顺序查询性能更高。
在B+树中有两个头指针,一个指向根节点,一个指向最小的叶子节点。所有叶子节点是一个环形的有序链式结构,因此可以对B+Tree进行两种查找,
- 对于主键的范围查找和分页查找
- 从根节点开始,进行随机查找
聚簇索引:
MYSQL中索引数据和实际行数据存放在一起,所以数据表中必须得有一个主键,这样构建索引时能将索引和数据进行组织起来。 Innodb是通过B+Tree结构对主键创建索引,然后叶子节点中存储行记录,如果没有主键,那么会选择唯一键,如果没有唯一键,那么会选择一个6字节的唯一ID的rowId作为主键。 创建表时最好让主键自增,因为前面我们看到叶子节点是按照顺序排列,主键自增的话,每次往末尾追加就可以了,如果当前默认的叶子节点满了,这时才会进行索引分裂,但是当我们主键不是自增的,每次是一个随机值,那么首先第一步得先去查找到对应的插入位置,如果在中间位置产生索引分裂,会造成整个索引树重新排列,这样的话构建索引的代价就大了。 如果一个表中除了主键,还有别的索引列,也称之为辅助索引,对该索引列创建索引时,会单独创建一个B+Tree,非叶子节点存储的是索引列,叶子节点存储的是索引列+主键。当我们根据这个索引列查找数据时,会先根据索引列查询到该行记录的主键,然后再根据主键去查找对应的行记录。这个过程查找了两个B+Tree,一个是索引列的B+tree,一个是主键索引的+Tree,我们这个过程叫做回表。
三、几个索引的技术名词
3.1 最左匹配原则
最左匹配原则是应用在组合索引中,组合索引就是多个数据列联合索引,比如对表person中(name,age)创建联合索引,最左匹配原则就是在查询时先匹配name字段,再匹配age字段,顺序不能颠倒。构建B+Tree索引时的排列顺序也是先根据name字段做排序,如果name字段相同,再根据age字段排序。组合索引的存储结构如下图:
如下图所示展示了最左匹配原则:
如上图所示,当单纯的以age为查询条件时,会进行全表扫描,而根据name查询可以使用索引,两个条件组合,不论顺序,都可以用索引,因为sql执行前,会对sql进行优化,所以最终顺序还是name在前面。 3.2 索引覆盖
如果基于辅助索引查询数据的时候,叶子节点中保存的刚好是要查询的数据字段,这个过程叫做索引覆盖,前面我们说过辅助索引中的叶子节点存储的记录的主键ID,如果我们sql语句只是通过name查询主键ID,那么只差辅助索引的那个B+Tree就可以返回结果,不用再去主键索引的B+Tree中查询数据了。 3.3 索引下推
在上面的创建的组合索引(name,age),当根据name和age查询时,在mysql5.6(不包含5.6)之前先根据name把所有的数据查询出来,然后再Server端内存中筛选age并返回符合条件的数据。 在mysql5.6之后的版本,从存储引擎拉取数据时,会根据name和age两个字段做筛选,将符合条件的结果查询出来。毋庸置疑,这种方式效率高。 3.4 谓词下推
两张表关联查询某些字段时,有两种执行方式:
- 先对两张表做关联,再取出要查询的字段值
- 先将要查询的字段都查询出来,然后再根据关系字段做关联
上面两种方式第二种的效率更高,这就叫做谓词下推。
四、索引优化
4.1 MYSQL的执行计划
通过explain命令来分析SQL的执行过程,看有没有需要优化的地方,来提高SQL的执行效率。执行explain,可以获得以下执行计划中的信息,如下图:
1)id
Select查询语句的序号,是个数字,表示查询中执行Select子句或者操作表的执行顺序,执行顺序分为以下三种情况:
- 如果ID相同,则顺序从上到下
- 如果ID不同,若是子查询,ID号会递增,ID值越大,优先级越高,越先被执行
- 如果ID有相同也有不同的,相同可以认为是一组,可以顺序往下执行,在所有组中,ID值越大,优先级越高,越先被执行
2)select_type
用来分辨查询的类型,是普通查询还是联合查询还是子查询。有如下几种取值:
3) table
对应访问哪一张表,可能是真实的表名、别名或者union合并结果集
- 如果是表名或者别名,则表示是从真实的表中获取数据。
- 如果是derivedN,则表示使用id为N的查询产生的派生表
- 当有union result时,表名是union n1,n2这种形式,n1和n2表示参与union的ID
4)type
type表示的是访问类型,表示以何种方式访问数据,最暴力的是全表扫描一张表的数据,效率最慢,访问类型有很多种,效率从块到慢依次是:
各种类型的值:
5) possible keys 显示可能应用于这张表的索引,一个或多个,查询字段中索引会被列出,但不一定使用。
6)key 查询中用到的索引,可能为空,若为覆盖索引,则和select中的字段名称重叠
7)key_len 索引字节数,可计算查询中使用的索引长度,在不损失精度的情况下,越短越好。
8)ref 显示索引的哪一列被使用了,可能是一个常数
9)rows 根据表的统计及索引使用情况,大致估算出所需记录需要读出的行数,直接反应sql找了多少数据,在完成目的情况下越少越好。
10)extra 包含的额外信息:
4.2 优化思路
- schema与数据类型优化
- 数据类型优化
- 主键选择
- 使用范式和反范式
- 数据冗余
- 存储引擎选择