我们的文章会在微信公众号IT民工的龙马人生和博客网站( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢!
由于博客中有大量代码,通过页面浏览效果更佳。
今天在给一个网友优化一条含有子查询的SQL时,用到了子查询转外连接的改写技术,被网友质疑改写where过滤行有问题。通过了解后,原来是网友对Where过滤行的理解有分歧导致的。这个分歧就是Where条件过滤行到底是对关联后的结果集进行过滤还是对关联前表上的行进行过滤呢?这个问题相信大部分DBA可能都会忽略的重大知识点,特别是原来在传统的Oracle纯运维的DBA尤为突出。今天就针对网友理解的这个知识点在PG环境中演示一下,通过执行计划来演示和说明一下。
1,结论
这里把结论放在前面,感兴趣的可以直接看结论,节约大家的时间。
- 等值连接中where过滤条件是过滤表中的记录。
- 在外连接中where过滤被关联表的列的IS NULL过滤时是对关联后的结果集进行过滤,其它是对过滤表中的记录。
2,创建测试表
这里就直接利用pg_class来创建两张测试表。
htz=# create table source as select * from pg_class;
SELECT 480
htz=# create table target as select *from pg_class;
SELECT 483
3,等值连接
3.1 模拟需求
查询source中relname为account,并且oid在target中存在的记录
htz=# explain analyze select count(*) from source a join target b using(oid) where a.relname='account';
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Aggregate (cost=36.67..36.68 rows=1 width=8) (actual time=0.164..0.165 rows=1 loops=1)
-> Hash Join (cost=18.01..36.66 rows=1 width=0) (actual time=0.083..0.160 rows=1 loops=1)
Hash Cond: (b.oid = a.oid)
-> Seq Scan on target b (cost=0.00..16.83 rows=483 width=4) (actual time=0.011..0.055 rows=483 loops=1)
-> Hash (cost=18.00..18.00 rows=1 width=4) (actual time=0.062..0.062 rows=1 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Seq Scan on source a (cost=0.00..18.00 rows=1 width=4) (actual time=0.008..0.059 rows=1 loops=1)
Filter: (relname = 'account'::name)
Rows Removed by Filter: 479
Planning Time: 0.113 ms
Execution Time: 0.229 ms
(11 rows)
其实上面的SQL可以等价改写为:
explain analyze select count(*) from (select * from source where relname='account') a join target b using(oid);
执行计划如下:
htz=# explain analyze select count(*) from (select * from source where relname='account') a join target b using(oid);
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
Aggregate (cost=36.67..36.68 rows=1 width=8) (actual time=0.586..0.588 rows=1 loops=1)
-> Hash Join (cost=18.01..36.66 rows=1 width=0) (actual time=0.345..0.582 rows=1 loops=1)
Hash Cond: (b.oid = source.oid)
-> Seq Scan on target b (cost=0.00..16.83 rows=483 width=4) (actual time=0.062..0.264 rows=483 loops=1)
-> Hash (cost=18.00..18.00 rows=1 width=4) (actual time=0.256..0.257 rows=1 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Seq Scan on source (cost=0.00..18.00 rows=1 width=4) (actual time=0.026..0.234 rows=1 loops=1)
Filter: (relname = 'account'::name)
Rows Removed by Filter: 479
Planning Time: 2.100 ms
Execution Time: 0.778 ms
(11 rows)
这里注意关键行的信息:
-> Seq Scan on source (cost=0.00..18.00 rows=1 width=4) (actual time=0.026..0.234 rows=1 loops=1)
Filter: (relname = 'account'::name)
Rows Removed by Filter: 479
这里说明在source全表扫描,扫描完成后通过relname=’account’来筛选满足条件的行,其中不满足条件的行被Filter删除了479行。
3.2 等值查询的结论
所以在等值查询中Where后面的过滤条件是直接过滤表的行。
4,外连接情况
外连接会涉及到2张表的关联,where中对2张表的过滤会带来完全不同的效果。这里我们对外连接中需要保留记录的表叫主库表,另外一张表就叫被连接表,比如在a left join b,这a就是主表,b就是被连接表;a right join b,b就是主表,a就是被连接表。where中对被驱动表中的列进行is null判断时,是对关联后的结果集进行过滤,对其它情况的过滤跟上面等值连接是一样的效果。
4.1 模拟非is null过滤需求
这里模拟一个需求就是查询target中relname为account的记录,利用oid对source进行关联,如果在source中存在,就展示source中的信息,如果不存在source返回null记录。
select * from target a left join source b using(oid) where a.relname='account';
执行计划的信息如下:
htz=# explain analyze select * from target a left join source b using(oid) where a.relname='account';
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Hash Right Join (cost=18.05..36.66 rows=1 width=526) (actual time=0.340..0.557 rows=1 loops=1)
Hash Cond: (b.oid = a.oid)
-> Seq Scan on source b (cost=0.00..16.80 rows=480 width=265) (actual time=0.015..0.179 rows=480 loops=1)
-> Hash (cost=18.04..18.04 rows=1 width=265) (actual time=0.285..0.285 rows=1 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Seq Scan on target a (cost=0.00..18.04 rows=1 width=265) (actual time=0.074..0.266 rows=1 loops=1)
Filter: (relname = 'account'::name)
Rows Removed by Filter: 482
Planning Time: 0.444 ms
Execution Time: 0.737 ms
(10 rows)
结果跟等值连接是一样的。
4.2 结果
结论跟等值连接一样
4.3
这里模拟一个需求就是查询target中存在oid记录,如果oid在source中不存在。
select * from target a left join source b using(oid) where b.oid is null;
对应的执行计划如下:
htz=# explain analyze select * from target a left join source b using(oid) where b.oid is null;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Hash Anti Join (cost=22.80..41.47 rows=3 width=526) (actual time=1.273..1.399 rows=3 loops=1)
Hash Cond: (a.oid = b.oid)
-> Seq Scan on target a (cost=0.00..16.83 rows=483 width=265) (actual time=0.025..0.114 rows=483 loops=1)
-> Hash (cost=16.80..16.80 rows=480 width=265) (actual time=1.021..1.021 rows=480 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 102kB
-> Seq Scan on source b (cost=0.00..16.80 rows=480 width=265) (actual time=0.046..0.508 rows=480 loops=1)
Planning Time: 0.305 ms
Execution Time: 1.650 ms
这里注意几个关键行的信息:
----------------------------------------
Hash Anti Join (cost=22.80..41.47 rows=3 width=526) (actual time=1.273..1.399 rows=3 loops=1)
优化器将SQL语句重新为反连接(not in /not exists),这个地方的where b.oid is null就是对整个结果集的过滤。上面的SQL语句其实可以重写为下面SQL:
select * from target a where not exists ( select * from source b where a.oid=b.oid);
改写后的执行计划如下:
htz=# explain analyze select * from target a where not exists ( select * from source b where a.oid=b.oid);
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------
Hash Anti Join (cost=22.80..41.47 rows=3 width=265) (actual time=0.111..0.132 rows=3 loops=1)
Hash Cond: (a.oid = b.oid)
-> Seq Scan on target a (cost=0.00..16.83 rows=483 width=265) (actual time=0.007..0.032 rows=483 loops=1)
-> Hash (cost=16.80..16.80 rows=480 width=4) (actual time=0.064..0.065 rows=480 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 25kB
-> Seq Scan on source b (cost=0.00..16.80 rows=480 width=4) (actual time=0.003..0.037 rows=480 loops=1)
Planning Time: 0.126 ms
Execution Time: 0.221 ms
(8 rows)
4.4 结论
在外连接中对被连接列进行IS NULL过滤时是对关联后的结果集进行判断和过滤。
4 总结论
关于整个实验的结论,可以参考第一部分,这里简单写一下关于怎么快速的进行上面判断,只需要看执行计划是否存在表上面有Filter过滤即可。
——————作者介绍———————–
姓名:黄廷忠
现就职:Oracle中国高级服务团队
曾就职:OceanBase、云和恩墨、东方龙马等
电话、微信、QQ:18081072613
个人博客: (http://www.htz.pw)
CSDN地址: (https://blog.csdn.net/wwwhtzpw)
博客园地址: (https://www.cnblogs.com/www-htz-pw)
SQL改写:99%DBA估计都会忽略的重大知识点:等您坐沙发呢!