sql优化(查询条件的变化对执行计划的影响)

    今天在工作中统计一个数据,最开始比较顺利的执行完了sql,但后来用户稍稍调整了需求,本来我以为增加一个查询条件就可以搞定的事情,结果执行了二十多分钟才出结果。后来我查看了下执行计划,发现前后两个sql的执行计划有变化。

    这两个SQL分别为:

SELECT T.YEARID, SUM(T.EXPORTSUM)
  FROM STDW.F_CUSTOM_EXPORTDETAIL T,
       (SELECT A.ZONE, A.ZONE_NAME
          FROM STDW.D_CUSTOM_PROVINCE_ZONE   A,
               STDW.D_CUSTOM_BRANCH_PROVINCE B
         WHERE A.PROVINCE_NO = B.PROVICEID
           AND B.CORPID = 4400) T2
 WHERE T.CITYNO = T2.ZONE
   AND (T.CUSTOMCODE8 LIKE '03061%' OR T.CUSTOMCODE8 LIKE '03%' OR
       (T.CUSTOMCODE8 LIKE '16%' AND T.CUSTOMCODE8 NOT LIKE '1601%' AND
       T.CUSTOMCODE8 NOT LIKE '1602%'))
   AND T.YEARID BETWEEN 2010 AND 2012
 GROUP BY T.YEARID
SELECT T.YEARID, SUM(T.EXPORTSUM)
  FROM STDW.F_CUSTOM_EXPORTDETAIL T,
       (SELECT A.ZONE, A.ZONE_NAME
          FROM STDW.D_CUSTOM_PROVINCE_ZONE   A,
               STDW.D_CUSTOM_BRANCH_PROVINCE B
         WHERE A.PROVINCE_NO = B.PROVICEID
           AND B.CORPID = 4400
           AND B.PROVICEID = 44) T2
 WHERE T.CITYNO = T2.ZONE
   AND (T.CUSTOMCODE8 LIKE '03061%' OR T.CUSTOMCODE8 LIKE '03%' OR
       (T.CUSTOMCODE8 LIKE '16%' AND T.CUSTOMCODE8 NOT LIKE '1601%' AND
       T.CUSTOMCODE8 NOT LIKE '1602%'))
   AND T.YEARID BETWEEN 2010 AND 2012
 GROUP BY T.YEARID


    第一个SQL的执行计划为:

    第二个SQL执行计划为:

    这里需要说明一下背景,表F_CUSTOM_EXPORTDETAIL是一个按年进行分区的表,每个分区下都是千万级别以上的数据量,差不多都是三、四千万的数据量。对于表D_CUSTOM_BRANCH_PROVINCE,CORPID字段为4400的数据有两条,PROVICEID字段为44的数据只有一条。

    在第一次只限制CORPID=4400时,SQL执行时间差不多2分钟(毕竟表F_CUSTOM_EXPORTDETAIL较大),看执行计划还比较合理。但在第二次再增加限制条件"B.PROVICEID = 44"后,SQL执行时间明显变长,差不多要20多分钟。通过分析这两个SQL的执行计划,发现第二条SQL的执行计划有变化:表的连接方式变成了NESTED LOOPS。如果表数据量大的时候,这种方式是比较耗时的。

    之前遇到的情况是相同的SQL,相隔一段时间后执行计划发生了变化,原因在于统计信息没有及时收集,解决方案是利用了oralce的hint。但这次和之前的情况不太一样,毕竟SQL不太一样,且这两条SQL的执行计划都是固定的。为了摸清根源,这次没有利用hint,而是去分析了第二条SQL。

    前面也提到,增加了条件"B.PROVICEID = 44"后,D_CUSTOM_BRANCH_PROVINCE只有一条记录,这时ORACLE会自作聪明的进行使用NESTED LOOPS表连接方式,实际上也是有道理的,但是因为最后关联的表F_CUSTOM_EXPORTDETAIL数据量过大,才导致执行效率低下。在这种情况下比较好的办法是使用HASH JOIN表连接方式,怎么样才能做到呢?根据前面的分析,我们应该首先考虑到破除只有一条记录这种可能。分析第二条SQL里面的子查询:

SELECT A.ZONE, A.ZONE_NAME
          FROM STDW.D_CUSTOM_PROVINCE_ZONE   A,
               STDW.D_CUSTOM_BRANCH_PROVINCE B
         WHERE A.PROVINCE_NO = B.PROVICEID
           AND B.CORPID = 4400
           AND B.PROVICEID = 44

    由于D_CUSTOM_BRANCH_PROVINCE限制条件后只有一条记录,且D_CUSTOM_PROVINCE_ZONE和该表进行关联时用到了PROVICEID,那我们可以不用D_CUSTOM_BRANCH_PROVINCE,直接限制表D_CUSTOM_PROVINCE_ZONE的条件,如下:

SELECT A.ZONE, A.ZONE_NAME
          FROM STDW.D_CUSTOM_PROVINCE_ZONE A
         WHERE A.PROVINCE_NO = 44

    这下就完全和表D_CUSTOM_BRANCH_PROVINCE脱离了干系,但实际效果是一样的。

    最后SQL修改为:

SELECT T.YEARID 年份, SUM(T.EXPORTSUM) 出口金额
  FROM STDW.F_CUSTOM_EXPORTDETAIL T,
       (SELECT A.ZONE, A.ZONE_NAME
          FROM STDW.D_CUSTOM_PROVINCE_ZONE A
         WHERE A.PROVINCE_NO = 44) T2
 WHERE T.CITYNO = T2.ZONE
   AND (T.CUSTOMCODE8 LIKE '03061%' OR T.CUSTOMCODE8 LIKE '03%' OR
       (T.CUSTOMCODE8 LIKE '16%' AND T.CUSTOMCODE8 NOT LIKE '1601%' AND
       T.CUSTOMCODE8 NOT LIKE '1602%'))
   AND T.YEARID BETWEEN 2010 AND 2012
 GROUP BY T.YEARID

    执行计划为:


    这下执行SQL又只要2分钟左右了。

    总结:

    1、理论上来说可选择性越高,SQL执行效率越高,但也要分情况:如果查询的结果只有一条了,可能会影响到表之间的关联方式,导致执行效率低下。

    2、查看执行计划时要多注意表之间的连接方式,而且要多想想ORACLE为什么要采用此方式,这样有助于解决问题。

    3、通过执行计划可以看出,在访问detail表时,如果不走分区,效率会非常低。因此在优化时,应尽量让分区表走分区。

你可能感兴趣的:(oracle)