下面这些sql都含有子查询:

mysql> select * from t1 where a in (select a from t2);
mysql> select * from (select * from t1) as t;

按返回的结果集区分子查询

1、标量子查询

那些只返回一个单一值的子查询称之为标量子查询。比如:

select * from t1 where a in (select max(a) from t2);

2、行子查询

返回一条记录的子查询,不过这条记录需要包含多个列。比如:

select * from t1 where (a, b) = (select a, b from t2 limit 1);

3、列子查询

返回一个列的数据的子查询,包含多条记录。比如:

select * from t1 where a in (select a from t2);

4、表子查询

子查询的结果既包含很多条记录,又包含很多个列。比如:

select * from t1 where (a, b) in (select a,b from t2);

按与外层查询关系来区分子查询

1、相关子查询

如果子查询的执行需要依赖于外层查询的值,我们就可以把这个子查询称之为相关子查询。比如:

select * from t1 where a in (select a from t2 where t1.a = t2.a);

2、不相关子查询

如果子查询可以单独运行出结果,而不依赖于外层查询的值,我们就可以把这个子查询称之为不相关子查询。前边介绍的那些子查询全部都可以看作不相关子查。

子查询在MySQL中是怎么执行的

1、对于不相关标量子查询或者行子查询

比如:select * from t1 where a = (select a from t2 limit 1);

它的执行步骤是:

1)执行select a from t2 limit 1这个子查询。

2)然后在将上一步子查询得到的结果当作外层查询的参数再执行外层查询select * from t1 where a = …;

2、对于相关标量子查询或者行子查询

比如:select * from t1 where b = (select b from t2 where t1.a = t2.a limit 1);

它的执行步骤是:

1)先从外层查询中获取一条记录,本例中也就是先从t1表中获取一条记录。

2)然后从上一步骤中获取的那条记录中找出子查询中涉及到的值,本例中就是从t1表中获取的那条记录中找出t1.a列的值,然后执行子查询。

3)最后根据子查询的查询结果来检测外层查询WHERE子句的条件是否成立,如果成立,就把外层查询的那条记录加入到结果集,否则就丢弃。

4)再次执行第一步,获取第二条外层查询中的记录,依次类推。。。

3、IN子查询优化

mysql对IN子查询进行了优化。

比如:select * from t1 where a in (select a from t2);

对于不相关的IN子查询来说,如果子查询的结果集中的记录条数很少,那么把子查询和外层查询分别看成两个单独的单表查询效率还是蛮高的,但是如果单独执行子查询后的结果集太多的话,就会导致这些问题:

• 结果集太多,可能内存中都放不下

• 对于外层查询来说,如果子查询的结果集太多,那就意味着IN子句中的参数特别多,这会导致:

• 无法有效的使用索引,只能对外层查询进行全表扫描。

• 在对外层查询执行全表扫描时,由于IN子句中的参数太多,这会导致检测一条记录是否符合和IN子句中的参数匹配花费的时间太长

在mysql中,不直接将不相关子查询的结果集当作外层查询的参数,而是将该结果集写入一个临时表里。写入临时表的过程是这样的:

1)该临时表的列就是子查询结果集中的列。

2)写入临时表的记录会被去重。IN语句是判断某个操作数在不在某个集合中,集合中的值重不重复对整个IN语句的结果并不影响,所以我们在将结果集写入临时表时对记录进行去重可以让临时表变得更小。临时表也是个表,只要为表中记录的所有列建立主键或者唯一索引就可以进行去重。

3)一般情况下子查询结果集不会特别大,所以会为它建立基于内存的使用Memory存储引擎的临时表,而且会为该表建立哈希索引。IN语句的本质就是判断某个操作数在不在某个集合里,如果集合中的数据建立了哈希索引,那么这个匹配的过程就是很快的。

4)如果子查询的结果集非常大,超过了系统变量tmp_table_size或者max_heap_table_size,临时表会转而使用基于磁盘的存储引擎来保存结果集中的记录,索引类型也对应转变为B+树索引。

这个将子查询结果集中的记录保存到临时表的过程称之为物化(Materialize)。那个存储子查询结果集的临时表称之为物化表。正因为物化表中的记录都建立了索引(基于内存的物化表有哈希索引,基于磁盘的有B+树
索引),通过索引执行IN语句判断某个操作数在不在子查询结果集中变得非常快,从而提升了子查询语句的性能。

还是对于上面的那个sql:

mysql> select * from t1 where a in (select a from t2);

当我们把子查询进行物化之后,假设子查询物化表的名称为materialized_table,该物化表存储的子查询结果集的列为m_val,那么这个查询其实可以从下边两种角度来看待:

• 从表t1的角度来看待,整个查询的意思其实是:对于t1表中的每条记录来说,如果该记录的a列的值在子查询对应的物化表中,则该记录会被加入最终的结果集。

• 从子查询物化表的角度来看待,整个查询的意思其实是:对于子查询物化表的每个值来说,如果能在t1表中找到对应的a列的值与该值相等的记录,那么就把这些记录加入到最终的结果集。

也就是说其实上边的查询就相当于表t1和子查询物化表materialized_table进行内连接:

select * from t1 inner join materialized_table on t1.a = m_val;

转化成内连接之后,查询优化器就可以评估不同连接顺序需要的成本是多少,选取成本最低的那种查询方式执行查询。

虽然将子查询进行物化之后再执行查询会有建立临时表的成本,但是可以将子查询转换为JOIN还是会更有效率一点的。那能不能不进行物化操作直接把子查询转换为连接呢。

我们对比下面两个sql:

select * from t1 where a in (select a from t2);
select t1.* from t1 inner join t2 on t1.a = t2.a;

这两个sql的查询结果其实很像,只是说对于第二个sql的结果集没有去重,所以IN子查询和两表连接之间并不完全等价,但是将子查询转换为连接又真的可以充分发挥优化器的作用,所以MySQL提出了一个新概念半连接(semi-join),将t1表和t2表进行半连接的意思就是:对于t1表的某条记录来说,我们只关心在t2表中是否存在与之匹配的记录是否存在,而不关心具体有多少条记录与之匹配,最终的结果集中只保留t1表的记录。semi-join只是在MySQL内部采用的一种执行子查询的方式,MySQL并没有提供面向用户的semi-join语法 。

那么怎么实现semi-join呢?

(1)Table pullout (子查询中的表上拉)

当子查询的查询列表处只有主键或者唯一索引列时,可以直接把子查询中的表上拉到外层查询的FROM子句中,并把子查询中的搜索条件合并到外层查询的搜索条件中。
比如:select * from t1 where a in (select a from t2 where t2.b = 1); – a是主键

我们可以直接把t2表上拉到外层查询的FROM子句中,并且把子查询中的搜索条件合并到外层查询的搜索条件中,上拉之后的查询就是这样的:

select * from t1 inner join t2 on t1.a = t2.a where t2.b = 1; -– a是主键

(2)DuplicateWeedout execution strategy (重复值消除)

对于这个查询来说:

select * from t1 where a in (select e from t2 where t2.b = 1); – e只是一个普通字段

转换为半连接查询后,t1表中的某条记录可能在t2表中有多条匹配的记录,所以该条记录可能多次被添加到最后的结果集中,为了消除重复,我们可以建立一个临时表,比方说这个临时表长这样:

CREATE TABLE tmp (
id PRIMARY KEY
);

这样在执行连接查询的过程中,每当某条t1表中的记录要加入结果集时,就首先把这条记录的主键值加入到这个临时表里,如果添加成功,说明之前这条t1表中的记录并没有加入最终的结果集,现在把该记录添加到最终的结果集;如果添加失败,说明这条之前这条t1表中的记录已经加入过最终的结果集,这里直接把它丢弃就好了,这种使用临时表消除semi-join结果集中的重复值的方式称之为DuplicateWeedout。

(3)FirstMatch execution strategy (首次匹配)

FirstMatch是一种最原始的半连接执行方式,就是我们最开始的思路,先取一条外层查询的中的记录,然后到子查询的表中寻找符合匹配条件的记录,如果能找到一条,则将该外层查询的记录放入最终的结果集并且停止查找更多匹配的记录,如果找不到则把该外层查询的记录丢弃掉;然后再开始取下一条外层查询中的记录,重复上边这个过程。

(4)LooseScan(松散索引扫描)

子查询扫描了非唯一索引,因为是非唯一索引,所以可能有相同的值,可以利用索引去重。

对于某些使用IN语句的相关子查询,比方这个查询:

select * from t1 where a in (select b from t2 where t1.b = t2.b);

它可以转换为半连接:

select * from t1 semi join t2 on t1.a = t2.a and t1.b = t2.b;

如一下几种情况就不能转换为semi-join:

  • 外层查询的WHERE条件中有其他搜索条件与IN子查询组成的布尔表达式使用OR连接起来

  • 使用NOT IN而不是IN的情况

  • 子查询中包含GROUP BY、HAVING或者聚集函数的情况

  • 子查询中包含UNION的情况

那么对于不能转为semi-join查询的子查询,有其他方式来进行优化:

  • 对于不相关子查询来说,可以尝试把它们物化之后再参与查询
比如对于使用了NOT IN下面这个sql:

select * from t1 where a not in (select a from t2 where t2.a = 1);

请注意这里将子查询物化之后不能转为和外层查询的表的连接,因为用的是not in只能是先扫描t1表,然后对t1表的某条记录来说,判断该记录的a值在不在物化表中。

  • 不管子查询是相关的还是不相关的,都可以把IN子查询尝试专为EXISTS子查询
其实对于任意一个IN子查询来说,都可以被转为EXISTS子查询,通用的例子如下:

outer_expr IN (SELECT inner_expr FROM … WHERE subquery_where)

可以被转换为:

EXISTS (SELECT inner_expr FROM … WHERE subquery_where AND outer_expr=inner_expr)

这样转换的好处是,转换前本来不能用到索引,但是转换后可能就能用到索引了,比如:

select * from t1 where a in (select a from t2 where t2.e = t1.e);

这个sql里面的子查询时用不到索引的,转换后变为:

select * from t1 where exists (select 1 from t2 where t2.e = t1.e and t1.a = t2.a)

转换之后t2表就能用到a字段的索引了。

所以,如果IN子查询不满足转换为semi-join的条件,又不能转换为物化表或者转换为物化表的成本太大,那么它就会被转换为EXISTS查询。

对于派生表的优化

select * from (select a, b from t1) as t;

上面这个sql,子查询是放在from后面的,这个子查询的结果相当于一个派生表,表的名称是t,有a,b两个字段。

对于派生表,有两种执行方式:

(一)把派生表物化

我们可以将派生表的结果集写到一个内部的临时表中,然后就把这个物化表当作普通表一样参与查询。当然,在对派生表进行物化时,使用了一种称为延迟物化的策略,也就是在查询中真正使用到派生表时才回去尝试物化派生表,而不是还没开始执行查询就把派生表物化掉。比如:

select * from (select * from t1 where a = 1) as derived1 inner join t2 on derived1.a = t2.a where t2.a =10;

如果采用物化派生表的方式来执行这个查询的话,那么执行时首先会到t1表中找出满足t1.a = 10的记录,如果找不到,说明参与连接的t1表记录就是空的,所以整个查询的结果集就是空的,所以也就没有必要去物化查询中的派生表了。

(二)将派生表和外层的表合并,也就是将查询重写为没有派生表的形式

比如下面这个sql:

select * from (select * from t1 where a = 1) as t;

和下面的sql是等价的:

select * from t1 where a = 1;

再看一些复杂一点的sql:

select * from (select * from t1 where a = 1) as t inner join t2 on t.a = t2.a where t2.b = 1;

我们可以将派生表与外层查询的表合并,然后将派生表中的搜索条件放到外层查询的搜索条件中,就像下面这样:

select * from t1 inner join t2 on t1.a = t2.a where t1.a = 1 and t2.b = 1;

这样通过将外层查询和派生表合并的方式成功的消除了派生表,也就意味着我们没必要再付出创建和访问临时表的成本了。可是并不是所有带有派生表的查询都能被成功的和外层查询合并,当派生表中有这些语句就不可以和外层查询合并:

聚集函数,比如MAX()、MIN()、SUM()啥的
DISTINCT
GROUP BY
HAVING
LIMIT
UNION 或者 UNION ALL
派生表对应的子查询的SELECT子句中含有另一个子查询

所以MySQL在执行带有派生表的时候,优先尝试把派生表和外层查询合并掉,如果不行的话,再把派生表物化掉执行查询。

Mysql查询优化器之关于子查询的优化的更多相关文章

  1. MySQL子查询的优化

    本文基于MySQL5.7.19测试 创建四张表,pt1.pt2表加上主键 mysql> create table t1 (a1 int, b1 int); mysql> create ta ...

  2. MySQL 子查询(四)子查询的优化、将子查询重写为连接

    MySQL 5.7 ref ——13.2.10.10优化子查询 十.子查询的优化 开发正在进行中,因此从长远来看,没有什么优化建议是可靠的.以下列表提供了一些您可能想要使用的有趣技巧.See also ...

  3. 高性能MySQL笔记 第6章 查询性能优化

    6.1 为什么查询速度会慢   查询的生命周期大致可按照顺序来看:从客户端,到服务器,然后在服务器上进行解析,生成执行计划,执行,并返回结果给客户端.其中“执行”可以认为是整个生命周期中最重要的阶段. ...

  4. mysql update不支持子查询更新

    先看示例: SELECT uin,account,password,create_user_uin_tree FROM sys_user 结果: 表中的create_user_uin_tree标识该条 ...

  5. 如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案

    如何优化Mysql千万级快速分页,limit优化快速分页,MySQL处理千万级数据查询的优化方案

  6. Mysql查询优化器之关于JOIN的优化

    连接查询应该是比较常用的查询方式,连接查询大致分为:内连接.外连接(左连接和右连接).自然连接 下图展示了 LEFT JOIN.RIGHT JOIN.INNER JOIN.OUTER JOIN 相关的 ...

  7. mysql关联、子查询索引优化

    1.驱动表:加索引不起作用,因为全表扫描.表1 left join 表2 ,此时表1是驱动表 被驱动表:给这个加索引.  关联查询  子查询时 尽量不使用not in 或者not exists 而是用 ...

  8. 优化系列 | DELETE子查询改写优化

    0.导读 有个采用子查询的DELETE执行得非常慢,改写成SELECT后执行却很快,最后把这个子查询DELETE改写成JOIN优化过程 1.问题描述 朋友遇到一个怪事,一个用子查询的DELETE,执行 ...

  9. MySQL百万级数据分页查询及优化

    方法1: 直接使用数据库提供的SQL语句 语句样式: MySQL中,可用如下方法: SELECT * FROM 表名称 LIMIT M,N 适应场景: 适用于数据量较少的情况(元组百/千级) 原因/缺 ...

随机推荐

  1. C++奇异递归模板模式

    虚函数的问题 虚函数的主要问题是性能开销比较大,一个虚函数调用可能需要花费数倍于非虚函数调用的时间,尤其是当非虚函数被声明为inline时(注意,虚函数不能被内联). CRTP介绍 CRTP的全称是C ...

  2. MySQL架构原理之运行机制

    所谓运行机制即MySQL内部就如生产车间如何进行生产的.如下图: 1.建立连接,通过客户端/服务器通信协议与MySQL建立连接.MySQL客户端与服务端的通信方式是"半双工".对于 ...

  3. Wi-Fi DFS与TPC介绍

    DFS与TPC是wifi认证的其中一项测试内容,如果不需要DFS功能,可以不进行测试,但是某些属于DFS频段的wifi信道则不允许使用. 1. 什么是WIFI Auto DFS? 通俗的说就是:躲雷达 ...

  4. [c/c++]c++控制台操作(基本操作的代码)

    本文转自:http://blog.csdn.net/stude/article/details/7645056 控制台窗口界面编程控制 〇.摘要一.概述二.控制台文本窗口的一般控制步骤三.控制台窗口操 ...

  5. RFC2899广播吞吐量测试——网络测试仪实操

    一.简介 RFC 2889为LAN交换设备的基准测试提供了方法学,它将RFC 2544中为网络互联设备基准测试所定义的方法学扩展到了交换设备,提供了交换机转发性能(Forwarding Perform ...

  6. Tabluea、Smartbi可视化仪表盘创建流程图分享

    你知道Tableau.Smartbi在可视化仪表盘制作步骤上有何差异吗?下面一起来了解吧~ 根据上面的流程图我们可以了解到,不同于Smartbi是在同一界面即可完成的,Tableau是由很多个工作表组 ...

  7. 想上BI系统,原来的Excel优秀模板都丢弃吗?

    我们公司之前一直都用Excel来存储数据,展示数据,Excel本身拥有强大的公式可以帮助我们的数据进行再加工计算,Excel的图形我们可以可以直接拿来展示数据,Excel本身还有数据透视表帮助我们的分 ...

  8. 一图胜千言,想让数据产生影响力,必须拥有好上手的BI数据分析工具

    当杂乱无章的数据,经过数据清洗后,得到了想用的数据,但是查看这些数据通过数据库只能看到数据本身,无法看到其中的规律,可以通过BI数据分析工具,图形化展示数据,使数据更形象化的展现在用户面前,更容易看出 ...

  9. shell脚本创建身份证号

    --作者:飞翔的小胖猪 --创建时间:2021年5月16日 --修改时间:2021年5月16日 说明 运行脚本,用户手动输入信息生成身份证号.该程序的核心在于函数模块化及select的使用. 注意:该 ...

  10. 解决Js中的resize事件执行两次的方法

    问题: 页面自适应的时候需要用到js的resize事件,但在执行过程中发现只要触发resize事件就会执行2次 原生js: window.onresize = function(){ console. ...