SQL优化 第七章 查询转换 4 Filter转换

参考资料:
本系列博客主要参考资料有CUUG冉乃纲老师数据库教学笔记,《SQL优化核心思想》(罗炳森,黄超,钟侥著),《PostgreSQL技术内幕:查询优化深度探索》(张树杰著),排名不分先后。

4 Filter转换

4.1 环境准备

create table tests1 as select * from dba_objects;

create table tests2 as select * from tests1;

CREATE INDEX IX_tests1_ID ON tests1(OBJECT_ID);

CREATE INDEX IX_tests2_ID ON tests2(OBJECT_ID);

4.2 Filter说明及初步改善

下面请大家执行一下这条SQL

EXPLAIN PLAN FOR SELECT COUNT(*) FROM tests1 T1

扫描二维码关注公众号,回复: 6471491 查看本文章

WHERE EXISTS (SELECT 1

              FROM tests2 T2

              WHERE T1.OBJECT_TYPE=T2.OBJECT_TYPE

                AND T2.OWNER='SCOTT'

              UNION ALL

              SELECT 1 FROM tests2 T3

              WHERE T1.OBJECT_ID=T3.OBJECT_ID

                AND T3.OBJECT_TYPE='DIRECTORY');

首先解释什么是Filter,Filter的计算方法类似嵌套循环,即外表走一条,子查询执行一次,子查询依赖外表,但是于嵌套循环不同的是,嵌套循环是结果集和结果集(当然这个被驱动的最好是表或者物化视图,因为可以走索引)的连接方式,可以理解为连接的两端都是固定的结果集,而Filter是结果集和查询的嵌套循环,被驱动部分无法构成一个独立的结果集。SQL优化器本身一般也是不太喜欢Filter,在产生执行计划时尽量展开Filter,上面的SQL之所以产生Filter,就是因为传入的条件不一样,优化器没法展开。

我们加上hint unnest试一下,强制让Filter展开,

EXPLAIN PLAN FOR SELECT COUNT(*) FROM tests1 T1

WHERE EXISTS (SELECT /*+ UNNEST*/ 1

              FROM tests2 T2

              WHERE T1.OBJECT_TYPE=T2.OBJECT_TYPE

                AND T2.OWNER='SCOTT'

              UNION ALL

              SELECT /*+ UNNEST*/ 1 FROM tests2 T3

              WHERE T1.OBJECT_ID=T3.OBJECT_ID

                AND T3.OBJECT_TYPE='DIRECTORY');

加hint后,并没有什么用,因为优化器已经很努力的干掉这种性能很差的Filter了,之所以还在,因为优化器已经无能为力了。

从上面的执行计划来看,大家说Filter是性能杀手,还是有依据的,上面的执行计划,我们不用看任何成本,大表驱动子查询一条,就知道这个计划废了。另外,很重要一点就是,这种执行计划是优化器很难改变的,执行计划固定,本身也是一个雷,有可能没有立即爆,但随着数据的变化,在某一天爆,因为优化器已经无法改变Filter执行计划了。

当然,还是要说明,hint unnest有时候还是有用的,不能一棍子打死,如果没用,oracle会把这个hint直接删掉的,存在即是合理嘛。

那怎么办,人为改写SQL。

SELECT COUNT(*) FROM

(SELECT *  FROM tests1 T1

WHERE EXISTS (SELECT 1

              FROM tests2 T2

              WHERE T1.OBJECT_TYPE=T2.OBJECT_TYPE

                AND T2.OWNER='SCOTT')

UNION

SELECT * FROM tests1 T1

WHERE EXISTS (SELECT 1 FROM tests2 T3

              WHERE T1.OBJECT_ID=T3.OBJECT_ID

                AND T3.OBJECT_TYPE='DIRECTORY'));

改写之后就好多了,该Hash时Hash,该嵌套时嵌套。

4.3 Filter与其他连接比较

之前,一直在讨论Filter的不好,那Filter有没有好的一面呢?优化器也是很努力的,或者时优化器的开发者很努力,即使百般不好,还是极力的去优化Filter连接。那我么看看Filter好的一面及优化器是怎么优化Filter的。

4.3.1 环境准备

create table tests3 as select rownum as id ,d.* from dba_objects d ;

create table tests4 as select d.* from tests3 d ;

update tests3 t3 set t3.id=mod(t3.id,100) where t3.id <=3000;

commit;

CREATE INDEX IX_tests3_OBJID ON TESTS3(OBJECT_ID);

CREATE INDEX IX_tests4_OBJID ON TESTS4(OBJECT_ID);

4.3.2 Filter与其他连接比较

(1)Filter连接

SELECT COUNT(*)

FROM TESTS3 T3

WHERE EXISTS

   (SELECT /*+ NO_UNNEST*/ T4.ID

    FROM  TESTS4 T4

    WHERE T3.ID=T4.ID)

AND T3.OBJECT_ID<=3000;

(2)嵌套半连接

SELECT /*+ LEADING(T3) USE_NL(T4@Q)*/ COUNT(*)

FROM TESTS3 T3

WHERE EXISTS

   (SELECT /*+ QB_NAME(Q)*/ T4.ID

    FROM  TESTS4 T4

    WHERE T3.ID=T4.ID)

  AND T3.OBJECT_ID<=3000;

(3)Hash半连接

SELECT COUNT(*)

FROM TESTS3 T3

WHERE EXISTS

   (SELECT T4.ID

    FROM  TESTS4 T4

    WHERE T3.ID=T4.ID)

AND T3.OBJECT_ID<=3000;

(4)Hash连接

SELECT  COUNT(*)

FROM TESTS3 T3 ,TESTS4 T4

  WHERE T3.ID=T4.ID

AND T3.OBJECT_ID<=3000;

(5)嵌套循环

SELECT /*+ LEADING(T3) USE_NL(T4)*/ COUNT(*)

FROM TESTS3 T3 ,TESTS4 T4

  WHERE T3.ID=T4.ID

AND T3.OBJECT_ID<=3000;

比较一:Filter和嵌套循环

Filter连接算法类似嵌套循环,那么不同点在哪里呢?关键字,Hash Table。

Filter只返回主表的数据,也就是和半连接一样,数据不会因为被驱动表的连接列值重复而产生数据翻倍,那么,可以建立一个Hash表,在连接列匹配成功,存入Hash表,比如本例的T3.ID,T4.ID都等于1时,把(1(T3.ID),1(T4.ID)放入Hash表,下一次T3.ID再取到1时,先从Hash表找结果,如果找到,不再执行子查询,那么,此时,如果走Filter连接,在外表连接列重复值很多时,就会大大减少子查询的执行次数,比如本例构造的数据,tests3表OBJECT_ID<=3000时,id列有29或30个重复值,Filter性能高于嵌套循环。

比较二:Filter和嵌套半连接

这两个执行计划性能一样,因为被驱动表走了相同的连接方式,同时我猜测,嵌套半连接内部也维护了Hash表。

比较三:Filter和Hash,Hash Semi

因为本案例被驱动表连接列没有索引,被驱动表只能全表扫,所以,走Hash或Hash Semi性能更好。因为被驱动表tests4的id列无重复值,所以hash和hash semi也是一样的。

我们继续进行下一轮比较,

首先,我们创建索引,

CREATE INDEX IX_tests3_ID ON TESTS3(ID);

CREATE INDEX IX_tests4_ID ON TESTS4(ID);

(1)

SELECT COUNT(*)

FROM TESTS3 T3

WHERE EXISTS

   (SELECT /*+ NO_UNNEST*/ T4.ID

    FROM  TESTS4 T4

    WHERE T3.ID=T4.ID

      AND T4.OBJECT_TYPE='TABLE')

  AND T3.OBJECT_ID<=1000;

(2)

SELECT COUNT(*)

FROM TESTS3 T3

WHERE EXISTS

   (SELECT T4.ID

    FROM  TESTS4 T4

    WHERE T3.ID=T4.ID

      AND T4.OBJECT_TYPE='TABLE')

AND T3.OBJECT_ID<=1000;

(3)

SELECT /*+ LEADING(T3) USE_NL(T4@Q)*/ COUNT(*)

FROM TESTS3 T3

WHERE EXISTS

   (SELECT /*+ QB_NAME(Q)*/ T4.ID

    FROM  TESTS4 T4

    WHERE T3.ID=T4.ID

      AND T4.OBJECT_TYPE='TABLE')

AND T3.OBJECT_ID<=1000;

本轮比较:

嵌套循环半连接>Filter>Hash,因为本案例中最终返回数据量小,被驱动表还可以走索引,符合嵌套循环优化的前提。

通过本章节,我们得出一个结论:

4.4 Filter转换小结

我们先中途小结以下,目的是为了下一章节的优化案例做好理论基础。

(1)如果符合嵌套循环优化的前提(小表驱大表,大表走索引),并且主表连接列重复值较多,可以将Filter展开成为半连接;

(2)如果符合嵌套循环优化的前提(小表驱大表,大表走索引),并且主表连接列重复不多,可以将Filter展开成为内连接,当然,要考虑被驱动表去重成本;

(3)如果符合Hash连接优化的前提(返回值较多),可以展开为Hash或Hash Semi连接;

(4)Filter优化有时候要改写SQL。

猜你喜欢

转载自blog.csdn.net/songjian1104/article/details/91349974