查询优化器对子查询一般采用嵌套执行的方式,即父查询中的每一行,都要执行一次子查询,这样子查询会执行很多次,效率非常低。

例如 exists、not exists 逐行取出经行匹配处理,项目中使用子查询的地方非常多,如何写出高效的sql,掌握子查询的优化是非常有必要的。

一、需要了解的概念:

PostgreSQL数据库基于子查询所在的位置和作用的不同,将子查询细分成了两类,一类称为子连接(SubLink),另一类称为子查询(SubQuery)。

如何区分子连接和子查询?

通常而言,如果它是以范围表的方式存在的,那么就称为子查询。

explain
select e1.*
from emp e1,
(select * from emp where deptno = 10) e2
where e1.empno = e2.empno Hash Join (cost=1.21..2.40 rows=3 width=41)
Hash Cond: (e1.empno = emp.empno)
-> Seq Scan on emp e1 (cost=0.00..1.14 rows=14 width=41)
-> Hash (cost=1.18..1.18 rows=3 width=5)
-> Seq Scan on emp (cost=0.00..1.18 rows=3 width=5)
Filter: (deptno = '10'::numeric)

如果它以表达式的方式存在,那么就称为子连接。

-- 子链接1
explain
select e.empno, (select avg(sal) from emp e1 where e.deptno = e1.deptno)
from emp e Seq Scan on emp e (cost=0.00..17.94 rows=14 width=37)
SubPlan 1
-> Aggregate (cost=1.19..1.20 rows=1 width=32)
-> Seq Scan on emp e1 (cost=0.00..1.18 rows=5 width=5)
Filter: (e.deptno = deptno) -- 子链接2
explain
select *
from emp e1
where sal in (select sal from emp where e1.deptno = 10) Seq Scan on emp e1 (cost=0.00..9.43 rows=7 width=41)
Filter: (SubPlan 1)
SubPlan 1
-> Result (cost=0.00..1.14 rows=14 width=5)
One-Time Filter: (e1.deptno = '10'::numeric)
-> Seq Scan on emp (cost=0.00..1.14 rows=14 width=5)

一般情况下,子连接的执行效率往往是比子查询低很多,因为子连接是逐行处理,会产生filter,而PostgreSQL 优化器会在某些情况下对子连接进行提升为子查询,从而对filter操作进行消除,提升查询效率。

二、in 子连接

** in 子连接提升子查询写法:**

explain select * from emp where deptno in (select deptno from dept);

Hash Join  (cost=1.09..2.31 rows=14 width=41)
Hash Cond: (emp.deptno = dept.deptno)
-> Seq Scan on emp (cost=0.00..1.14 rows=14 width=41)
-> Hash (cost=1.04..1.04 rows=4 width=5)
-> Seq Scan on dept (cost=0.00..1.04 rows=4 width=5)

可以被提升,优化器相会内部重写成内连接。

explain select emp.* from emp inner join dept on emp.deptno = dept.deptno;

Hash Join  (cost=1.09..2.31 rows=14 width=41)
Hash Cond: (emp.deptno = dept.deptno)
-> Seq Scan on emp (cost=0.00..1.14 rows=14 width=41)
-> Hash (cost=1.04..1.04 rows=4 width=5)
-> Seq Scan on dept (cost=0.00..1.04 rows=4 width=5)

表明此 in 子连接被优化,优化后采用hash join算法。

in 子连接无法提升子查询写法:

-- 子连接包含谓词过滤写法,无法提升子查询
explain select * from emp e1 where sal in (select sal from emp where e1.deptno = 10); Seq Scan on emp e1 (cost=0.00..9.43 rows=7 width=41)
Filter: (SubPlan 1)
SubPlan 1
-> Result (cost=0.00..1.14 rows=14 width=5)
One-Time Filter: (e1.deptno = '10'::numeric)
-> Seq Scan on emp (cost=0.00..1.14 rows=14 width=5)
-- 使用 not in 也是无法提升子查询,not in与 <> all含义相同

explain select * from emp e1 where sal not in (select sal from emp );

Seq Scan on emp e1  (cost=1.18..2.35 rows=7 width=41)
Filter: (NOT (hashed SubPlan 1))
SubPlan 1
-> Seq Scan on emp (cost=0.00..1.14 rows=14 width=5)

表明此 in 子连接未被优化,无法消除filter操作,只能逐行处理。

三、exists 子连接

** exists 子连接提升子查询写法:**

explain
select e.* from emp e where exists(select * from emp e2 where e.empno = e2.empno); Hash Join (cost=1.32..2.50 rows=14 width=41)
Hash Cond: (e.empno = e2.empno)
-> Seq Scan on emp e (cost=0.00..1.14 rows=14 width=41)
-> Hash (cost=1.14..1.14 rows=14 width=5)
-> Seq Scan on emp e2 (cost=0.00..1.14 rows=14 width=5) -- 当加入where e.deptno = 10 条件在子链接时,仍然支持上拉
explain
select e.*
from emp e
where exists(select * from emp e2 where e.deptno = 10) Nested Loop Semi Join (cost=0.00..2.45 rows=3 width=41)
-> Seq Scan on emp e (cost=0.00..1.18 rows=3 width=41)
Filter: (deptno = '10'::numeric)
-> Materialize (cost=0.00..1.21 rows=14 width=0)
-> Seq Scan on emp e2 (cost=0.00..1.14 rows=14 width=0)

exists 子连接无法提升子查询写法:

explain
select e.*
from emp e
where exists(select sum(sal) from emp e2 where e.empno = e2.empno); Seq Scan on emp e (cost=0.00..17.80 rows=7 width=41)
Filter: (SubPlan 1)
SubPlan 1
-> Aggregate (cost=1.18..1.19 rows=1 width=32)
-> Seq Scan on emp e2 (cost=0.00..1.18 rows=1 width=5)
Filter: (e.empno = empno) explain
select e.*
from emp e
where exists(select * from emp e2 where e2.deptno = 10); Result (cost=0.39..1.53 rows=14 width=41)
One-Time Filter: $0
InitPlan 1 (returns $0)
-> Seq Scan on emp e2 (cost=0.00..1.18 rows=3 width=0)
Filter: (deptno = '10'::numeric)
-> Seq Scan on emp e (cost=0.39..1.53 rows=14 width=41) explain
select e.* from emp e where not exists(select 1 from emp e2 ) Result (cost=0.08..1.22 rows=14 width=41)
One-Time Filter: (NOT $0)
InitPlan 1 (returns $0)
-> Seq Scan on emp e2 (cost=0.00..1.14 rows=14 width=0)
-> Seq Scan on emp e (cost=0.08..1.22 rows=14 width=41)

in和exists都存在不被优化的可能,对于in和exists的选择,当父查询结果集小于子查询结果集则选择exists,如果父查询结果集大于子查询结果集选择in。

四、所有的all子链接都不支持上拉

explain
select * from emp where sal > all (select sal from emp e2); Seq Scan on emp (cost=0.00..9.89 rows=7 width=41)
Filter: (SubPlan 1)
SubPlan 1
-> Materialize (cost=0.00..1.21 rows=14 width=5)
-> Seq Scan on emp e2 (cost=0.00..1.14 rows=14 width=5) explain
select * from emp where sal = all (select sal from emp e2); Seq Scan on emp (cost=0.00..9.89 rows=7 width=41)
Filter: (SubPlan 1)
SubPlan 1
-> Materialize (cost=0.00..1.21 rows=14 width=5)
-> Seq Scan on emp e2 (cost=0.00..1.14 rows=14 width=5) explain
select * from emp where sal < all (select sal from emp e2); Seq Scan on emp (cost=0.00..9.89 rows=7 width=41)
Filter: (SubPlan 1)
SubPlan 1
-> Materialize (cost=0.00..1.21 rows=14 width=5)
-> Seq Scan on emp e2 (cost=0.00..1.14 rows=14 width=5)

关于all的查询都都是以子连接的形式,不会上拉。

some和any是等效的,这里不做演示了。

五、join与子查询固化或rewrite

join或子查询的优化,属于优化器优化JOIN的范畴。
当用户的QUERY涉及到多个JOIN对象,或者涉及到多个子查询时,优化器可以选择是否改变当前的SQL,产生更多的plan选择更优的执行计划。
postgresql.conf文件中:
#from_collapse_limit = 8 当from列表的对象少于from_collapse_limit时,优化器可以将子查询提升到上层进行JOIN,从而可能选择到更优的执行计划。
#join_collapse_limit = 8 # 1 disables collapsing of explicit
# JOIN clauses 当使用显示的JOIN时(除了full join),例如a join b join c join d,优化器可以重排JOIN的顺序,以产生更多的PLAN选择更优的执行计划。
如果join_collapse_limit=1,则不重排,使用SQL写法提供的顺序。
如果用户要固化JOIN顺序,请使用显示的JOIN,同时将join_collapse_limit设置为1。
如果用户不打算提升子查询,同样的,将from_collapse_limit 设置为1即可。

六、等价改写

子查询中没有group by子句,也没有聚集函数,则可使用下面的等价转换
val>all(select...) to val>max(select...)
val<all(select...) to val<min(select...)
val>any(select...) to val>min(select...)
val<any(select...) to val<max(select...)
val>=all(select...) to val>=max(select...)
val<=all(select...) to val<=min(select...)
val>=any(select...) to val>=min(select...)
val<=any(select...) to val<=max(select...)
通常,聚集函数min(),max()的执行效率要比any、all效率高

七、ORACLE 的子查询非嵌套

子查询非嵌套(subquery Unnesting),查询转换技巧中强烈要求掌握的技能。

** 当where子查询中有in、not in、exits、not exists等,CBO会尝试将子查询展开(unnest),从而消除FILTER。这个过程叫作子查询非嵌套。子查询非嵌套的目的就是消除FILTER。**

当子查询语句含有exists或not exists时,子查询中有固化子查询关键词(union/union all / start connect by/rownum /cube / rollup),那么执行计划就很容易产生FILTER。

八、结束语

1、postgresql子查询的优化思路,子查询不用执行多次

2、优化器可以根据统计信息来选择不同的连接方法和不同的连接顺序

3、子查询中的连接条件,过滤条件分别变成了父查询的连接条件、过滤条件、优化器可以对这些条件进行下推、提高执行效率

4、将子查询优化为表连接后,子查询只需要执行一次、而优化器可以根据统计信息来选择不同的连接方式和连接顺序、子查询的连接条件和过滤条件分别变成父查询的条件。

5、这些查询中all是完全不支持上拉子子链接的,in和exists存在不被优化的可能。

6、not exists虽然没有被上拉,但是被优化为只执行一次,相对于not in稍好。

7、可使用等价改写的方式优化8.可根据配置文件,固化子查询,以及表的连接顺序。

8、无论是PostgreSQL中的子连接提升查询还是ORACLE中的子查询非嵌套主要的目的就是优化子查询,消除filter。

PostgreSQL 提升子连接与 ORACLE 子查询非嵌套的更多相关文章

  1. PLSQL Developer连接远程Oracle方法(非安装客户端)

    Oracle比较麻烦,通常需要安装oracle的客户端才能实现.通过instantclient可以比较简单的连接远程的Oracle. 1.新建目录D:\Oracle_Cleint用于存放相关文件,新建 ...

  2. PLSQL Developer连接远程Oracle方法(非安装client)

    远程连接Oracle比較麻烦,通常须要安装oracle的客户端才干实现. 通过instantclient能够比較简单的连接远程的Oracle. 1.新建文件夹D:\Oracle_Cleint用于存放相 ...

  3. 一道Oracle子查询小练习

    一道Oracle子查询小练习   昨天晚上躺在床上看Oracle(最近在学习这个),室友说出个题目让我试试.题目如下: 有如下表结构,请选择出成绩为前三名的人的信息(如果成绩相同,则算并列),表名为t ...

  4. Oracle子查询相关内容(包含TOP-N查询和分页查询)

    本节介绍Oracle子查询的相关内容: 实例用到的数据为oracle中scott用户下的emp员工表,dept部门表,数据如下: 一.子查询 1.概念:嵌入在一个查询中的另一个查询语句,也就是说一个查 ...

  5. oracle 子查询和组合函数

    oracle 子查询和组合函数 --查询与"SCOTT"在同一个部门的员工 select empno,ename,deptno from emp where deptno in ( ...

  6. Oracle子查询之简单子查询

    Oracle 简单子查询 顾名思义,简单子查询是嵌套在 SQL 语句中的另一个SELECT 语句,并且子查询只返回一列数据 1,单行子查询: 子查询 (内查询) 在主查询之前一次执行完成.子查询的结果 ...

  7. Oracle 子查询(复杂select语句)

    在执行数据操作时,如果某个操作需要依赖于另外一个 select语句的查询结果,那么就可以把 select 语句迁入到该操作语句中,这样就形成了一个子查询.实际应用中,表与表之间相互关联,相互依存,这样 ...

  8. oracle 子查询详解 in和exists的区别

    sql允许多层嵌套,子查询是嵌套在其他查询中的查询.我们可以把子查询当做一张表来看到,即外层语句可以把内嵌的查询结果当做一张表使用. 子查询查询结果有三种情况 不返回查询记录.若子查询不返回记录则主查 ...

  9. ylb:子查询(嵌套子查询)和子查询(相关子查询)

    ylbtech-SQL Server:SQL Server-子查询(嵌套子查询)和子查询(相关子查询) SQL Server 子查询(嵌套子查询)和子查询(相关子查询). 1,ylb:1,子查询(嵌套 ...

  10. SQL子连接案例

    子查询 何时使用子查询 1. 子查询作为数据源 2. 数据加工 需求:根据不同顾客的所有的账户余额划分区间,进行分组 sql语句实现如下: select 'Small Fry' name , 0 lo ...

随机推荐

  1. 用极限网关实现 ES 容灾,简单!

    身为 IT 人士,大伙身边的各种系统肯定不少吧.系统虽多,但最最最重要的那套.那几套,大伙肯定是捧在手心,关怀备至.如此重要的系统,万一发生故障了且短期无法恢复,该如何保障业务持续运行? 有过这方面思 ...

  2. Blazor提取出Razor类库,没有css的class的智能提示

    最开始从stackoverflow上找到了答案,有两种办法,但都不太理想 后来自己找了新的办法,其实很简单,把要用的css复制到Razor类库的wwwroot文件夹中,默认是不会复制到引用Razor类 ...

  3. 引入代码来源:深入分析markdown-it-quote插件的魔法

    引入代码来源:深入分析markdown-it-quote插件的魔法 markdown-it-quote是一个用于markdown-it的插件,支持多种代码围栏功能. 这是 SourceCodeTrac ...

  4. quarkus实战之八:profile

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus实战>系列 ...

  5. linux下的venv使用

    首先安装该模块: sudo apt-get install python3-venv 之后创建用于存储工程的文件夹 mkdir [filename] 创建环境: python3 -m venv ven ...

  6. trick : Trygub num

    trick大意 我对于这个trick的理解为:支持位运算的高精度 维护一个以 \(b\)为基数的大数 \(N\),并支持以下功能: 给定(可能是负)整数 \(|x|, |y| \leqslant n\ ...

  7. 使用文件批量find

    有时候需要找一批文件传到本地,文件名都不一样.可以先把文件名写到文件里面,一个文件名为一行. 比如: file1.wav file2.wav file3.wav 在命令行执行: for i in `c ...

  8. springboot整合nacos和dubbo

    0. 源码 源码: gitee 1. 版本 java: 1.8.0_281 nacos: 2.1.2 2. 创建项目 创建一个简单的springboot或者maven项目, 或者代码库(gitee/g ...

  9. 搭建 QT6+OpenCv4.7+CMake的环境

    本文主要介绍如何搭建QT6+OpenCv的开发环境,基本流程如下 先安装CMake3.27.3,用来编译适用用QT的OpenCv的源码,安装完成后要配置系统的环境变量 安装Qt6的开发环境,并配置环境 ...

  10. 聊聊数据库事务内嵌TCP连接

    最近再看项目代码,发现很多的service里面,喜欢在事务内部再去调用HTTP请求,简单分析下此种方式的利弊与解决策略. 概述 在数据库内部嵌套TCP连接(一般是HTTP调用或是RPC远程调用). @ ...