主要探讨
in exist
类型子查询在 mysql 的优化策略
总体分为:
- 关联子查询
- 非关联子查询
区别是什么?
非关联子查询:子查询中不涉及外层查询参数。很直接的想法:
- 先执行,得到结果集,外层循环它,过滤并加入最后的结果集
- 得到内层结果集可以优化 ->
物化
那关联子查询呢?首先它本身查询就是不完整的,查询闭包中包含一些外层查询提供的参数,至于知道这些参数才能运行该查询。
非关联 IN 子查询
SELECT * FROM s1
WHERE key1 IN (SELECT common_field FROM s2 WHERE key3 = 'a');
复制代码
- 子查询与外层不相关
- 外层 key in (子查询结果集)
直观的想法在上面已经说啦,说几个问题:
- 子查询结果集内存可能放不下
- 外层查询检测一条记录是否在结果集中:
id in (1..10000000)
这个其实类似for了
首先是不相关的,那就确实可以写在一个结果集中,主体就是对结果集进行优化:
- 写入临时表,列就是查询的列【这个不能被检测到】
- 结果集去重,尽可能的小
- 为了查询加速,为临时表建立
hash index
因为 hash index
的存在,所以查询就是判断 column value
是否在集合中。
当然结果集很大,超过临时表内存限制,内存临时表会转换为使用磁盘存储,hash index -> b+ index
。
这个过程就叫:物化
inner join
从上面的优化:
- 子查询物化成内存临时表【暂时这么定】
- 外层查询每一条记录循环匹配子查询临时表
整体来看就是:两个表之间的匹配操作,这个不就是《经典连接》吗?
SELECT s1.* FROM s1 INNER JOIN <materialized_table> ON key1 = m_val;
复制代码
那么会回到连接本身的成本计算了:首先这是 inner join
- 驱动表的选择
- 为驱动表和被驱动表选择成本最低的访问方法
连接成本 = 单次访问 <驱动表> 成本 + 驱动表循环次 ✖️ 单次 <被驱动表> 访问成本
成本计算的差别就在:
- s1
s1 num * materialized_table[mval=xxx] 的访问成本
:物化表有hash index
- 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
是什么?
下篇我们来说说。
未完待续。。。