Mysql索引查漏补缺

933 阅读4分钟

Mysql的索引知识点说起来感觉是一个大杂烩,但是总体的原则是根据使用情况来建立和使用索引

根据使用情况来建立索引,经常用到的比如:

  • 经常查询的列要建立索引
  • 多表关联时,要保证关联字段上要有索引

索引的存储:B+树

本文探讨的范围是索引中的普通索引,当对一个列创建索引也就是B+树之后,索引会包含该列的键值及键值对应行所在的 rowid。

null值存储

索引列的null值存在于B+树的最左边。目前常用版本的mysql,索引里面含有null是没有问题的。

普通索引特性(列的桶存储主键集合)

在建立索引时,有一个原则:区分度不大的字段上不宜建立索引。这有点像是基数排序桶排序的思想,通过索引查找到的每个桶里面的主键就是目标范围。

Mysql使用B+树来组织这颗索引树,如下所示是一个5阶索引分裂的示意图:

btree.png 如果通过索引找到的数据范围还是很大,回表或比较的开销还是很大,就要考虑是否还要走索引了(后面章节有具体的分析)。

更新索引树

通常来说,更新十分频繁的索引上不宜建立索引,如果索引的值发生变更,不仅主键索引指向的行的数据要修改,对应的B+树索引也会发生较多的数据移动来维持索引树的有序性和平衡性。

怎样命中索引

在查询的时候,即使查询条件命中了索引也不一定走索引树,我们可以使用expalin命令进行分析。

基本原则

命中索引是一个搜索B+树的过程,而SQL语法支持的查询语义特别多,像等值查询和范围查询都可以很方便的在B+树上进行查询。具体如下:

  • 等值查询也就是说:列=值刚好命中索引列,则可使用这个索引。
  • inbetweenunion, or 等语义查询也可以命中索引
  • 范围查询可以命中索引
    • 这也是有别于hashmap的树结构的特性,树结构天然支持范围查询

分析什么时候走索引

基本原则是,少于某个百分比的数据回表才走索引,比如说少于30%的数据回表才走索引,具体是使用索引还是不使用普通索引,使用哪个索引会根据CPU磁盘等的开销选择一个时间最优的计划。

通过索引中记录的 rowid 访问表中的数据就叫回表。回表次数太多会严重影响 SQL 性能,如果回表次数太多,就不应该走索引扫描,应该直接走全表扫描。 EXPLAIN命令结果中的Using Index意味着不会回表,通过索引就可以获得主要的数据。Using Where则意味着需要回表取数据

前面讲到了索引的区分度,在计算区分度的时候,要考虑计数和基数。

  • 计数:表总行数
  • 基数:某个列唯一值的个数 很明显,区分度=基数/计数,列的基数越大区分度越高。

如果多个列都命中的条件下,mysql也会选择一个区分度更大的索引去查询或者说开销更小的查询计划。

其他规则

等值查询和范围查询可以命中索引,但是他们的

  • 负向条件查询不能命中索引,比如!= 、not in和 <>
  • server执行函数计算不会命中索引,比如date(c)='xxx',c列上的索引将失去作用 注意这些目前来说还没有通融的余地,这可能是实现的问题,虽然有些函数可能可以优化,但是为了大局着想还是不便于优化的。

除此之外,还有两类索引值得特别注意,他们就是字符串索引复合索引

字符串索引

我们知道可以使用字符串索引,但是在使用字符串索引的时候经常会出现下面这个问题:

ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes

767bytes的长度限制是为什么呢?

Mysql索引的每个节点是一个Page,每个page 16K,如果不限制的话会导致索引的深度太大,一次查询的过程过于占用内存空间,反而使得使用索引的效率也不高。

而按照767bytes的限制,至少可以分裂出20阶树,4层的B+树可以存储大约16万数据量,5层可以存储320万数据量,从查询效率来看也是一个很好的折衷。

like

like查询是字符串查询可使用的SQL语法。语法如:

like '%xxx%'

mysql可支持like在前缀命中的情况下使用索引。

为什么能支持呢? 因为前缀命中在B+树里面刚好也是一个连续的范围。 这个范围可表示为这样的一个前闭后开区间:

[第一个命中前缀的字符串,第一个不命中前缀的字符串)

假设like要查询的前缀为abc,有下图这样的一个索引树结构:

like.png

在这个图中,先查到第一个满足前缀要求的字符串abc,然后沿着abc依次往后查询,直到第一个不满足前缀要求的字符串位置。所以,这里["abc","abd")之间的范围是被命中的范围,就是我们要查询的数据。

复合索引

复合索引是含有两个以上列组成的索引,列在索引中的顺序会影响索引的命中。

  • 列在索引中的顺序按照从左到右的顺序,左边的列被命中后,索引中更右边的列才有被命中的可能。
  • 复合索引如果利用的好,sql语句利用覆盖索引进行查询,可以进一步避免回表。这不仅对于普通的行查询有用,也可以用于group by和order等复杂sql查询语义中。

下面分析一个问题, 问题: 有一个复合索引a,b,c,有一个查询a=1,b>1,c=1,能用到这个索引吗?

分析

假设这个复合索引a,b,c的数据目前是这样的,那么对于查询a=1,b>1刚好命中是如下图红色部分的范围。

compose.png

因为还有c=1的条件,于是能命中的索引数据只有1,3,1这一个。

compose2.png

所以我认为问题的答案是能用到这个索引,而且三个条件a=1,b>1,c=1都可以发挥作用。

如果这个问题改一下: 问题: 有一个复合索引a,b,c,有一个查询b>1,c=1,能用到这个索引吗?

答案:不能

总结

本文本文探讨的范围是索引中的普通索引,分析了Mysql索引的一些问题,主要是关于怎么建立索引、索引的存储结构以及思考关于怎么命中索引的问题。

在细节上,是否了解到了这些?

  • 经常使用和联查的列要加索引
  • B+树特性支持范围查询
  • 字符串索引有长度限制
  • 字符串like前缀可使用索引
  • 复合查询也是前缀先匹配原则
  • 联合索引的覆盖索引可避免回表