MySQL中包含IN子句的语句是怎样执行的

标签: 公众号文章mysql


对于开发小伙伴来讲,对MySQL中的包含IN子句的语句确定熟悉的不能再熟悉了,几乎每天用,时时用。但是不少小伙伴并不知道包含IN子句的语句是怎样执行的,在一些查询优化的场景中就开始找不着北了,本篇文章就来唠叨一下MySQL中包含IN子句的语句是怎样执行的(以MySQL 5.7的InnoDB存储引擎为例)。算法

准备工做

为了故事的顺利发展,咱们先建立一个表:sql

CREATE TABLE t (
    id INT NOT NULL AUTO_INCREMENT,
    key1 VARCHAR(100),
    common_field VARCHAR(100),
    PRIMARY KEY (id),
    KEY idx_key1 (key1)
) Engine=InnoDB CHARSET=utf8;
复制代码

能够看到表t中包含两个索引:bash

  • id列为主键的聚簇索引
  • key1列创建的二级索引

这个表里边如今有10000条数据:优化

mysql> SELECT COUNT(*) FROM t;
+----------+
| COUNT(*) |
+----------+
|    10000 |
+----------+
1 row in set (0.00 sec)
复制代码

从B+树中定位记录

咱们如今想执行下边这个语句:ui

SELECT * FROM t WHERE 
    key1 >= 'b' AND key1 <= 'c';
复制代码

假设优化器选择使用二级索引来执行查询,那么查询语句的执行示意图就以下图所示:spa

image_1duk7ejao14qa17s3116e1lmjqt19.png-88.4kB

小贴士:原谅我把索引对应的复杂的B+树结构搞了一个极度精简版,为了突出重点,咱们忽略掉了页的结构,直接把全部的叶子节点的记录都放在一块儿展现。咱们想突出的重点就是:B+树叶子节点中的记录是按照索引列值大小排序的,对于的聚簇索引来讲,它对应的B+树叶子节点中的记录就是按照id列排序的,对于idx_key1二级索引来讲,它对应的B+树叶子节点中的记录就是按照key1列排序的。 3d

咱们想查询key1列的值在['b', 'c']这个区间中的记录,那么就须要:code

  • 先经过idx_key1索引对应的B+树快速定位到key1列值为'b'、而且最靠左的那条二级索引记录,该二级索引记录中包含着对应的主键值,根据这个主键值再到聚簇索引中定位到完整的记录(这个过程称之为回表),将其返回给server层,server层再发送给客户端。cdn

  • 记录按照键值由小到大的顺序排列成一个单链表的形式,因此咱们能够沿着这个单链表接着定位到下一条二级索引记录,而且执行回表操做,将完整的记录交给server层以后发送给客户端。

  • 继续沿着记录的单向链表查找,重复上述过程,直到找到的二级索引记录的key1列的值不知足key1 <= 'c'的这个条件,如图所示,也就是当咱们在idx_key1二级索引中找到了key1='ca'的那条记录后,发现它不符合key1 <= 'c'的条件,因此就中止查找。

上述过程就是经过B+树查找一个键值在某一个范围区间的记录的过程。

包含IN子句的执行过程

若是咱们想执行下边这个语句:

SELECT * FROM t WHERE 
    key1 IN ('b', 'c');
复制代码

若是优化器选择使用二级索引执行上述语句,那它是如何执行的呢?

优化器会将IN子句中的条件当作是2个范围区间(虽然这两个区间中都仅仅包含一个值):

  • ['b', 'b']
  • ['c', 'c']

那么在语句执行过程当中就须要经过B+树去定位两次记录所在的位置:

  • 先定位键值在范围区间['b', 'b']的记录:

    • 先经过idx_key1索引对应的B+树快速定位到key1列值为'b'、而且最靠左的那条二级索引记录,以后回表将其发送给server 层后再发送给客户端。

    • 再沿着记录组成的单链表把符合key1=b的二级索引记录找到,而且回表后发送给server层,以后再发送给客户端。

    • 重复上述过程,直到找到的二级索引记录的key1列的值不知足key1 = 'b'的这个条件为止。

  • 再定位键值在范围区间['c', 'c']的记录:

    查找过程相似,就很少赘述了。

因此若是你写的IN语句中的参数越多,意味着须要经过B+树定位记录的次数就越多。

IN子句中参数值重复的状况

比方说下边这条语句:

SELECT * FROM t WHERE 
    key1 IN ('b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b');
复制代码

虽然IN子句中包含好多个参数,但MySQL在语法解析的时候只会为其生成一个范围区间,那就是:['b', 'b']

IN子句的参数顺序问题

比方说下边这条语句:

SELECT * FROM t WHERE key1 IN ('c', 'b');
复制代码

IN ('c', 'b')和IN ('b', 'c')有啥差异么?也就是存储引擎在对待IN ('c', 'b')子句时,会先去找key1 = 'c'的记录,再去找key1 = 'b'的记录么?若是是这样的话,下边两条语句岂不是可能发生死锁:

事务T1中的语句一:
SELECT * FROM t WHERE 
    key1 IN ('b', 'c') FOR UPDATE;

事务T2中的语句二:
SELECT * FROM t WHERE 
    key1 IN ('c', 'b') FOR UPDATE;
复制代码

放心,在生成范围区间的时候,天然是将范围区间排了序,也就是即便条件是IN ('c', 'b'),那优化器也会先让存储引擎去找键值在['b', 'b']这个范围区间中的记录,而后再去找键值在['c', 'c']这个范围区间中的记录。

系统变量eq_range_index_dive_limit对IN子句的影响

你们必定要记着:MySQL优化器决定使用某个索引执行查询的仅仅是由于:使用该索引时的成本足够低。也就是说即便咱们有下边的语句:

SELECT * FROM t WHERE 
    key1 IN ('b', 'c');
复制代码

MySQL优化器须要去分析一下若是使用二级索引idx_key1执行查询的话,键值在['b', 'b']['c', 'c']这两个范围区间的记录共有多少条,而后经过必定方式计算出成本,与全表扫描的成本相对比,选取成本更低的那种方式执行查询。

在计算查询成本的这一步骤中你们须要注意,对于包含IN子句条件的查询来讲,须要依次分析一下每个范围区间中的记录数量是多少。MySQL优化器针对IN子句对应的范围区间的多少而指定了不一样的策略:

  • 若是IN子句对应的范围区间比较少,那么将率先去访问一下存储引擎,看一下每一个范围区间中的记录有多少条(若是范围区间的记录比较少,那么统计结果就是精确的,反之会采用必定的手段计算一个模糊的值,固然算法也比较麻烦,咱们就不展开说了,小册里有说MySQL是怎样运行的小册连接),这种在查询真正执行前优化器就率先访问索引来计算须要扫描的索引记录数量的方式称之为index dive。

  • 若是IN子句对应的范围区间比较多,这样就不能采用index dive的方式去真正的访问二级索引idx_key1(由于那将耗费大量的时间),而是须要采用以前在背地里产生的一些统计数据去估算匹配的二级索引记录有多少条(很显然根据统计数据去估算记录条数比index dive的方式精确性差了不少)。

那何时采用index dive的统计方式,何时采用index statistic的统计方式呢?这就取决于系统变量eq_range_index_dive_limit的值了,咱们看一下在个人机器上该系统变量的值:

mysql> SHOW VARIABLES LIKE 'eq_range_index_dive_limit';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| eq_range_index_dive_limit | 200   |
+---------------------------+-------+
1 row in set (0.20 sec)
复制代码

能够看到它的默认值是200,这也就意味着当范围区间个数小于200时,将采用index dive的统计方式,不然将采用index statistic的统计方式。

不过这里须要你们特别注意,在MySQL 5.7.3以及以前的版本中,eq_range_index_dive_limit的默认值为10。因此若是你们采用的是5.7.3以及以前的版本的话,很容易采用索引统计数据而不是index dive的方式来计算查询成本。当你的查询中使用到了IN查询,可是却实际没有用到索引,就应该考虑一下是否是因为 eq_range_index_dive_limit 值过小致使的。

题外话

本文首发于公众号「咱们都是小青蛙」。

写文章挺累的,有时候你以为阅读挺流畅的,那实际上是背后无数次修改的结果。若是你以为不错请帮忙转发一下,万分感谢~ 这里是个人公众号「咱们都是小青蛙」,里边有更多技术干货,时不时扯一下犊子,欢迎关注:

相关文章
相关标签/搜索