下面这些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. 【一天一个小知识10/20】Unity通过www获取json文本信息。

    前提:领导要我在unity获取局域网服务器的文本信息.给了一个json的网络文本让我测试.我对于json以及服务器比较陌生.就直接去网上找相关的资料. 以下是自己测试的代码,没问题. 测试的网络jso ...

  2. spring循环依赖的产生与解决

    1.循环依赖的产生 在spring中对象默认都是单例的 ,意味整个容器中只有一个该类的对象. 如图,B类有一个属性a,A类有一个属性b.当B类创建对象时,要给a属性赋值:当A类创建对象时,要给b属性赋 ...

  3. jenkins pipeline构建项目

    以前用的jenkins自由风格发布代码.界面丑陋,出现问题位置不够清晰.今天改进一下流程使用jenkins pipeline构建项目. 学习使我快乐 步骤一.安装pipeline插件 点击系统管理-& ...

  4. WPF 自定义文本框输入法 IME 跟随光标

    本文告诉大家在 WPF 写一个自定义的文本框,如何实现让输入法跟随光标 本文非小白向,本文适合想开发自定义的文本框,从底层开始开发的文本库的伙伴.在开始之前,期望了解了文本库开发的基础知识 本文实现的 ...

  5. hbuilderx快捷键、回到上一步、回到上次编辑处

    快捷键:Alt + 左/右箭头  其他快捷键:

  6. IP网络主动测评系统——IT运维人员的好帮手

    一.前 言 随着计算机网络的普及和快速发展,互联网已经融入到人们的衣食住行等方方面 面,如工作.购物.音视频聊天.视频会议.朋友圈.抖音.在线网游.网络电影 电视等.毫不夸张地说,现如今大部分人的绝大 ...

  7. 技术小白的也能独立完成数据分析,这款BI系统你值得拥有

    是否有很多小白跟我一样,不会编程代码,又觉得excel操作太繁琐了,一直苦苦不知道要怎么做数据分析.前段时间我使用了一款bi系统,简直太方便了!拖拉拽就能制作分析图表.点击就能应用智能分析功能,如果这 ...

  8. 《破碎的残阳,我们逆光》连载小说- HashMap剖析

    破碎的残阳,我们逆光[连载小说]- HashMap剖析 "行到水穷处,坐看云起时"        前言: 偶尔翻阅了自己当时高中时代写的日志,发现了几篇自己多年未打开的自写小说草本 ...

  9. 树莓派4B安装 百度飞桨paddlelite 做视频检测 (一、环境安装)

    前言: 当前准备重新在树莓派4B8G 上面搭载训练模型进行识别检测,训练采用了百度飞桨的PaddleX再也不用为训练部署环境各种报错发愁了,推荐大家使用. 关于在树莓派4B上面paddlelite的文 ...

  10. laravel 使用Redis

    1.非框架中使用redis实例? 就是new出redis对象然后连接,然后键值操作即可 <?php $redis = new redis(); $redis->connect('127.0 ...