子查询优化「一」|小册免费学

主要探讨 in exist 类型子查询在 mysql 的优化策略

总体分为:

  1. 关联子查询
  2. 非关联子查询

区别是什么?

非关联子查询:子查询中不涉及外层查询参数。很直接的想法:

  1. 先执行,得到结果集,外层循环它,过滤并加入最后的结果集
  2. 得到内层结果集可以优化 -> 物化

那关联子查询呢?首先它本身查询就是不完整的,查询闭包中包含一些外层查询提供的参数,至于知道这些参数才能运行该查询。

非关联 IN 子查询

SELECT * FROM s1 
    WHERE key1 IN (SELECT common_field FROM s2 WHERE key3 = 'a');
复制代码
  1. 子查询与外层不相关
  2. 外层 key in (子查询结果集)

直观的想法在上面已经说啦,说几个问题:

  1. 子查询结果集内存可能放不下
  2. 外层查询检测一条记录是否在结果集中:id in (1..10000000) 这个其实类似for了

首先是不相关的,那就确实可以写在一个结果集中,主体就是对结果集进行优化:

  1. 写入临时表,列就是查询的列【这个不能被检测到】
  2. 结果集去重,尽可能的小
  3. 为了查询加速,为临时表建立 hash index

因为 hash index 的存在,所以查询就是判断 column value 是否在集合中。

当然结果集很大,超过临时表内存限制,内存临时表会转换为使用磁盘存储,hash index -> b+ index

这个过程就叫:物化

inner join

从上面的优化:

  1. 子查询物化成内存临时表【暂时这么定】
  2. 外层查询每一条记录循环匹配子查询临时表

整体来看就是:两个表之间的匹配操作,这个不就是《经典连接》吗?

SELECT s1.* FROM s1 INNER JOIN <materialized_table> ON key1 = m_val;
复制代码

那么会回到连接本身的成本计算了:首先这是 inner join

  1. 驱动表的选择
  2. 为驱动表和被驱动表选择成本最低的访问方法

连接成本 = 单次访问 <驱动表> 成本 + 驱动表循环次 ✖️ 单次 <被驱动表> 访问成本

成本计算的差别就在:

  1. s1 s1 num * materialized_table[mval=xxx] 的访问成本:物化表有 hash index
  2. m_table materialized_table num * s1[key1] 访问成本:s1[key1] 可以走索引的话

所以就看这两个的访问成本差别了。

semi-join

上次在讨论成本的时候,不管是哪个作为驱动表,都必须先把子查询先 物化,然后开始连接。

进一步优化:把物化过程做掉。回到最上面的实例sql:

SELECT * FROM s1 
    WHERE key1 IN (SELECT key2 FROM s2 WHERE key3 = 'a');
复制代码

sql 最终的结果集选取的是:

s1 中找到可以在过滤后的 s2 中匹配的记录,匹配的条件是 s1.key1 = s1.key2

而具体 semi-join 是什么?

下篇我们来说说。


未完待续。。。

猜你喜欢

转载自juejin.im/post/6954596737986068494