子查询优化
自查询分类
按照返回的结果集区分子查询
标量子查询:
那些只返回一个单一值的子查询称之为标量子查询
SELECT (SELECT m1 FROM t1 LIMIT 1); SELECT * FROM t1 WHERE m1 = (SELECT MIN(m2) FROM t2);
行子查询
就是返回一条记录的子查询
不过这条记录需要包含多个列(只包含一个列就成了标量子查询了)
SELECT * FROM t1 WHERE (m1, n1) = (SELECT m2, n2 FROM t2 LIMIT 1);
列子查询
表子查询
按与外层查询关系来区分子查询
不相关子查询
如果子查询可以单独运行出结果,而不依赖于外层查询的值,我们就可以把这个子查询称之为不相关子查询
相关子查询
如果子查询的执行需要依赖于外层查询的值,我们就可以把这个子查询称之为相关子查询
子查询在MySQL中是怎么执行的
标量子查询、行子查询的执行方式
不相关标量子查询或者行子查询
SELECT * FROM s1 WHERE key1 = (SELECT common_field FROM s2 WHERE key3 = 'a' LIMIT 1);
-
先单独执行
(SELECT common_field FROM s2 WHERE key3 = 'a' LIMIT 1)
这个子查询。 -
然后在将上一步子查询得到的结果当作外层查询的参数再执行外层查询
SELECT * FROM s1 WHERE key1 = ...
。
也就是说,对于包含不相关的标量子查询或者行子查询的查询语句来说,MySQL会分别独立的执行外层查询和子查询,就当作两个单表查询就好了
相关的标量子查询或者行子查询
SELECT * FROM s1 WHERE key1 = (SELECT common_field FROM s2 WHERE s1.key3 = s2.key3 LIMIT 1);
-
先从外层查询中获取一条记录,本例中也就是先从
s1
表中获取一条记录。 -
然后从上一步骤中获取的那条记录中找出子查询中涉及到的值,本例中就是从
s1
表中获取的那条记录中找出s1.key3
列的值,然后执行子查询。 -
最后根据子查询的查询结果来检测外层查询
WHERE
子句的条件是否成立,如果成立,就把外层查询的那条记录加入到结果集,否则就丢弃。 -
再次执行第一步,获取第二条外层查询中的记录,依次类推~
也就是说对于一开始唠叨的两种使用标量子查询以及行子查询的场景中,MySQL
优化器的执行方式并没有什么新鲜的。
IN子查询优化
物化表
SELECT * FROM s1 WHERE key1 IN (SELECT common_field FROM s2 WHERE key3 = 'a');
将子查询转换为semi-join
将s1
表和s2
表进行半连接的意思就是:对于s1
表的某条记录来说,我们只关心在s2
表中是否存在与之匹配的记录是否存在,而不关心具体有多少条记录与之匹配,最终的结果集中只保留s1
表的记录
Table pullout (子查询中的表上拉)
当子查询的查询列表处只有主键或者唯一索引列时,可以直接把子查询中的表上拉
到外层查询的FROM
子句中,并把子查询中的搜索条件合并到外层查询的搜索条件中
SELECT * FROM s1 WHERE key2 IN (SELECT key2 FROM s2 WHERE key3 = 'a');
DuplicateWeedout execution strategy (重复值消除)
对于这个查询来说:
SELECT * FROM s1
WHERE key1 IN (SELECT common_field FROM s2 WHERE key3 = 'a');
转换为半连接查询后,s1
表中的某条记录可能在s2
表中有多条匹配的记录,所以该条记录可能多次被添加到最后的结果集中,为了消除重复,我们可以建立一个临时表,比方说这个临时表长这样:
CREATE TABLE tmp (
id PRIMARY KEY
);
这样在执行连接查询的过程中,每当某条s1
表中的记录要加入结果集时,就首先把这条记录的id
值加入到这个临时表里,如果添加成功,说明之前这条s1
表中的记录并没有加入最终的结果集,现在把该记录添加到最终的结果集;如果添加失败,说明之前这条s1
表中的记录已经加入过最终的结果集,这里直接把它丢弃就好了,这种使用临时表消除semi-join
结果集中的重复值的方式称之为DuplicateWeedout
。
LooseScan execution strategy (松散扫描)