天天看点

MySQL索引最左匹配原则及优化原理(中)

1 全值匹配

MySQL索引最左匹配原则及优化原理(中)

很明显,当按照索引中所有列进行精确匹配(精确匹配指“=”或“IN”匹配)时,索引可以被用到。

理论上

索引对顺序敏感

,但MySQL查询优化器会自动调整where子句的条件顺序以使用适合的索引。

  • 例如我们将where中的条件顺序颠倒
  • MySQL索引最左匹配原则及优化原理(中)
  • 效果一样。

2 最左前缀匹配

当查询条件精确匹配索引的左边连续一个或几个列时,如<emp_no>或<emp_no, title>,索引可以被用到,但是只能用到一部分,即条件所组成的最左前缀。

从分析结果看用到了PRIMARY索引,但是key_len为4,说明只用到了索引的第一列前缀

MySQL索引最左匹配原则及优化原理(中)

3 查询条件用到索引中列的精确匹配,但是中间某个条件未提供

MySQL索引最左匹配原则及优化原理(中)

此时索引使用情况和情况二相同,因为title未提供,所以查询只用到了索引的第一列,而后面的from_date虽然也在索引中,但是由于title不存在而无法和左前缀连接,因此需要对结果进行过滤from_date(这里由于emp_no唯一,所以不存在扫描)

如果想让from_date也使用索引而不是where过滤,可以增加一个辅助索引<emp_no, from_date>,此时上面的查询会使用这个索引。

此外,还可以使用一种称之为“隔离列”的优化方法,将emp_no与from_date之间的“坑”填上

首先我们看下title一共有几种不同的值

MySQL索引最左匹配原则及优化原理(中)

只有7种。在这种成为“坑”的列值比较少的情况下,可以考虑用“IN”来填补这个“坑”从而形成最左前缀

MySQL索引最左匹配原则及优化原理(中)

这次key_len为59,说明索引被用全了,但是从type和rows看出IN实际上执行了一个range查询,这里检查了7个key。看下两种查询的性能比较:

MySQL索引最左匹配原则及优化原理(中)

“填坑”后性能提升了一点。如果经过emp_no筛选后余下很多数据,则后者性能优势会更加明显。当然,如果title的值很多,用填坑就不合适了,必须建立辅助索引

4 查询条件没有指定索引第一列

MySQL索引最左匹配原则及优化原理(中)

由于不是最左前缀,这样的查询显然用不到索引

5 匹配某列的前缀字符串

MySQL索引最左匹配原则及优化原理(中)

此时可以用到索引,通配符%不出现在开头,则可以用到索引,但根据具体情况不同可能只会用其中一个前缀

6 范围查询(由于B+树的顺序特点,尤其适合)

MySQL索引最左匹配原则及优化原理(中)
  • 范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引
  • 索引最多用于一个范围列,因此如果查询条件中有两个范围列则无法全用到索引
  • MySQL索引最左匹配原则及优化原理(中)
  • 可以看到索引对第二个范围索引无能为力。这里特别要说明MySQL一个有意思的地方,那就是仅用explain可能无法区分范围索引和多值匹配,因为在type中这两者都显示为range
  • 用了“between”并不意味着就是范围查询,例如下面的查询:
  • MySQL索引最左匹配原则及优化原理(中)
  • 看起来是用了两个范围查询,但作用于emp_no上的“BETWEEN”实际上相当于“IN”,也就是说emp_no实际是多值精确匹配。可以看到这个查询用到了索引全部三个列。因此在MySQL中要谨慎地区分多值匹配和范围匹配,否则会对MySQL的行为产生困惑。
  • MySQL索引最左匹配原则及优化原理(中)

7 查询条件中含有函数或表达式

如果查询条件中含有函数或表达式,则MySQL不会为这列使用索引(虽然某些在数学意义上可以使用)

MySQL索引最左匹配原则及优化原理(中)

虽然这个查询和情况5中功能相同,但是由于使用了函数left,则无法为title列应用索引,而情况五中用LIKE则可以。再如:

MySQL索引最左匹配原则及优化原理(中)

显然这个查询等价于查询emp_no为10001的函数,但是由于查询条件是一个表达式,MySQL无法为其使用索引。看来MySQL还没有智能到自动优化常量表达式的程度,因此在写查询语句时尽量避免表达式出现在查询中,而是先手工私下代数运算,转换为无表达式的查询语句。

MySQL索引最左匹配原则及优化原理(中)

最左前缀可用于在索引中定位记录。那不符合最左前缀的部分,会怎么样?

以市民表的联合索引(name, age)为例。

  • 需求

    检索表中“名字第一个字是张,且年龄是10的所有男孩”

SQL:

select * from tuser where name like '张%' and age=10 and ismale=1;      

语句在搜索索引树时,只能用 “张”,找到第一个满足条件记录ID3。还不错,总比全表扫好。然后判断其他条件。

MySQL5.6前,只能从ID3开始个个回表,到主键索引上找数据行,再对比字段值。

5.6引入索引下推优化(index condition pushdown), 在索引遍历过程,对索引中包含的字段先做判断,直接过滤不满足条件的记录,减少回表。

这两个过程的执行流程图:

  • 无索引下推执行流程
  • MySQL索引最左匹配原则及优化原理(中)
  • 索引下推执行流程
  • MySQL索引最左匹配原则及优化原理(中)
  • 两个图里面,每一个虚线箭头表示回表一次。

无索引下推执行流程,在(name,age)索引里特意去掉age的值,这过程InnoDB并不看age的值,只按顺序把“name第一个字是’张’”的记录一条条取出来回表,回表4次。

区别是,InnoDB在(name,age)索引内部就开始判断了age是否等于10,对不等10的记录,直接判断并跳过。这个例子中,只需对ID4、ID5这两条记录回表取数据判断,只需回表2次。