Mysql学习-第二章(使用子查询)

mysql8.0之后对子查询的优化得到大幅提升

为什么使用子查询

更符合逻辑,如“找到1993年,没有下过订单的客户数量”

select count(c_custerkey) cnt
from
customer
where c_custerkey not in(
select o_custerkey
    from orders
    where 
    o_orderdate >= '1993-01-01'
    And
    o_orderdate < '1994-01-01'
)

通过notin查询不在订单表的用户有哪些

可以使用leftjoin(在customer存在,在orders不存在)

select count(c_custerkey) cnt
from 
customer
left join 
orders on
customer.c_custerkey = orders.o_custerkey
and o_orderdate >='1993-01-01'
and o_orderdate < '1994-01-01'
where 
o_custerkey is null

LEFT JOIN 更易于理解,能进行传统 JOIN 的两表连接,而子查询则要求优化器聪明地将其转换为最优的 JOIN 连接

l两种sql语句最终的执行计划

可以看到,不论是子查询还是 LEFT JOIN,最终都被转换成了 Nested Loop Join,所以上述两条 SQL 的执行时间是一样的。

即,在 MySQL 8.0 中,优化器会自动地将 IN 子查询优化,优化为最佳的 JOIN 执行计划,这样一来,会显著的提升性能。

子查询 IN 和 EXISTS

将上述列子中not in改为 not exists 比较

select count(c_custerkey) cnt
from
customer
where c_custerkey not exists(
select o_custerkey
    from orders
    where 
    o_orderdate >= '1993-01-01'
    And
    o_orderdate < '1994-01-01'
)

它和 NOT IN 的子查询执行计划一模一样,所以二者的性能也是一样的

子查询优化

在 MySQL 8.0 版本之前,MySQL 对于子查询的优化并不充分。所以在子查询的执行计划中会看到 DEPENDENT SUBQUERY 的提示,这表示是一个依赖子查询,子查询需要依赖外部表的关联。

如果你看到这样的提示,就要警惕, 因为 DEPENDENT SUBQUERY 执行速度可能非常慢,大部分时候需要你手动把它转化成两张表之间的连接。

计算出每个员工最后成交的订单时间

select * from orders
where 
(o_clerk,o_orderdate) in
(
    select o_clerk,Max(o_orderdate)
    from orders
    group by o_clerk
);

通过命令 EXPLAIN FORMAT=tree 输出执行计划,你可以看到,第 3 行有这样的提示:Select #2 (subquery in condition; run only once)。这表示子查询只执行了一次,然后把最终的结果保存起来了。

执行计划的第 6 行Index lookup on <materialized_subquery>,表示对表 orders 和子查询结果所得到的表进行 JOIN 连接,最后返回结果。

所以,当前这个执行计划是对表 orders 做2次扫描,每次扫描约 5587618 条记录:

  • 第 1 次扫描,用于内部的子查询操作,计算出每个员工最后一次成交的时间;

  • 第 2 次表 oders 扫描,查询并返回每个员工的订单信息,即返回每个员工最后一笔成交的订单信息。

最后,直接用命令 EXPLAIN 查看执行计划,如下图所示:

MySQL 8.0 版本执行过程

如果是老版本的 MySQL 数据库,它的执行计划将会是依赖子查询,执行计划如下所示:

老版本 MySQL 执行过程

对比 MySQL 8.0,只是在第二行的 select_type 这里有所不同,一个是 SUBQUERY,一个是DEPENDENT SUBQUERY。

接着通过命令 EXPLAIN FORMAT=tree 查看更详细的执行计划过程:

可以发现,第 3 行的执行技术输出是:Select #2 (subquery in condition; dependent),并不像先前的执行计划,提示只执行一次。另外,通过第 1 行也可以发现,这条 SQL 变成了 exists 子查询,每次和子查询进行关联。

所以,上述执行计划其实表示:先查询每个员工的订单信息,接着对每条记录进行内部的子查询进行依赖判断。也就是说,先进行外表扫描,接着做依赖子查询的判断。所以,子查询执行了5587618,而不是1次!!!

所以,两者的执行计划,扫描次数的对比如下所示:

对于依赖子查询的优化,就是要避免子查询由于需要对外部的依赖,而需要对子查询扫描多次的情况。所以可以通过派生表的方式,将外表和子查询的派生表进行连接,从而降低对于子查询表的扫描,从而提升 SQL 查询的性能。

那么对于上面的这条 SQL ,可将其重写为:

SELECT * FROM orders o1,
(
	SELECT 
		o_clerk, MAX(o_orderdate)
	FROM
		orders
	GROUP BY o_clerk
) o2
WHERE 
	o1.o_clerk = o2.o_clerk 
    AND o1.o_orderdate = o2.orderdate;

可以看到,我们将子查询改写为了派生表 o2,然后将表 o2 与外部表 orders 进行关联。关联的条件是:o1.o_clerk = o2.o_clerk AND o1.o_orderdate = o2.orderdate。
通过上面的重写后,派生表 o2 对表 orders 进行了1次扫描,返回约 5587618 条记录。派生表o1 对表 orders 扫描 1 次,返回约 1792612 条记录。这与 8.0 的执行计划就非常相似了,其执行计划如下所示:

最后,来看下上述 SQL 的执行时间:

可以看到,经过 SQL 重写后,派生表的执行速度几乎与独立子查询一样。所以,若看到依赖子查询的执行计划,记得先进行 SQL 重写优化哦。
知识点来自学习-姜承尧老师拉钩网教导内容。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值