帮盖尔优化SQL子查询优化的经典案例

 上周五要下班的时候,盖尔发来一个SQL

select tpc.policy_id,
       tcm.policy_code,
       tpf.organ_id,
       to_char(tpf.insert_time, 'YYYY-MM-DD') As insert_time,
       tpc.change_id,
       d.policy_code,
       e.company_name,
       f.real_name,
       tpf.fee_type,
       sum(tpf.pay_balance) as pay_balance,
       c.actual_type,
       tpc.notice_code,
       d.policy_type,
       g.mode_name as pay_mode
  from t_policy_change    tpc,
       t_contract_master  tcm,
       t_policy_fee       tpf,
       t_fee_type         c,
       t_contract_master  d,
       t_company_customer e,
       t_customer         f,
       t_pay_mode         g
 where tpc.change_id = tpf.change_id
   and tpf.policy_id = d.policy_id
   and tcm.policy_id = tpc.policy_id
   and tpf.receiv_status = 1 
   and tpf.fee_status = 1
   and tpf.payment_id is null
   and tpf.fee_type = c.type_id
   and tpf.pay_mode = g.mode_id
   and d.company_id = e.company_id(+)
   and d.applicant_id = f.customer_id(+)
   and tpf.organ_id in
       (select 
         organ_id
          from t_company_organ
         start with organ_id = '101'
        connect by prior organ_id = parent_id)
 group by tpc.policy_id,
          tpc.change_id,
          tpf.fee_type,
          to_char(tpf.insert_time, 'YYYY-MM-DD'),
          c.actual_type,
          d.policy_code,
          g.mode_name,
          e.company_name,
          f.real_name,
          tpc.notice_code,
          d.policy_type,
          tpf.organ_id,
          tcm.policy_code
 order by change_id, fee_type

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                           |  Name                       | Rows  | Bytes |TempSpc| Cost (%CPU)|
----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                             | 45962 |    11M|       | 45650   (0)|
|   1 |  SORT GROUP BY                      |                             | 45962 |    11M|    23M| 45650   (0)|
|*  2 |   HASH JOIN                         |                             | 45962 |    11M|       | 43908   (0)|
|   3 |    INDEX FULL SCAN                  | T_FEE_TYPE_IDX_003          |   106 |   636 |       |     1   (0)|
|   4 |    NESTED LOOPS OUTER               |                             | 45962 |    11M|       | 43906   (0)|
|*  5 |     HASH JOIN                       |                             | 45962 |  7271K|  6824K| 43905   (0)|
|   6 |      NESTED LOOPS                   |                             | 45961 |  6283K|       | 42312   (0)|
|*  7 |       HASH JOIN SEMI                |                             | 45961 |  5655K|    50M| 33120   (1)|
|*  8 |        HASH JOIN OUTER              |                             |   400K|    45M|    44M| 32315   (1)|
|*  9 |         HASH JOIN                   |                             |   400K|    39M|    27M| 26943   (0)|
|* 10 |          HASH JOIN                  |                             |   400K|    23M|       | 16111   (0)|
|  11 |           TABLE ACCESS FULL         | T_PAY_MODE                  |    25 |   525 |       |     2   (0)|
|* 12 |           TABLE ACCESS FULL         | T_POLICY_FEE                |   400K|    15M|       | 16107   (0)|
|  13 |          TABLE ACCESS FULL          | T_CONTRACT_MASTER           |  1136K|    46M|       |  9437   (0)|
|  14 |         VIEW                        | index_join_007            |  2028K|    30M|       |            |
|* 15 |          HASH JOIN                  |                             |   400K|    45M|    44M| 32315   (1)|
|  16 |           INDEX FAST FULL SCAN      | PK_T_CUSTOMER               |  2028K|    30M|       |   548   (0)|
|  17 |           INDEX FAST FULL SCAN      | IDX_CUSTOMER__BIR_REAL_GEN  |  2028K|    30M|       |   548   (0)|
|  18 |        VIEW                         | VW_NSO_1                    |     7 |    42 |       |            |
|* 19 |         CONNECT BY WITH FILTERING   |                             |       |       |       |            |
|  20 |          NESTED LOOPS               |                             |       |       |       |            |
|* 21 |           INDEX UNIQUE SCAN         | PK_T_COMPANY_ORGAN          |     1 |     6 |       |            |
|  22 |           TABLE ACCESS BY USER ROWID| T_COMPANY_ORGAN             |       |       |       |            |
|  23 |          NESTED LOOPS               |                             |       |       |       |            |
|  24 |           BUFFER SORT               |                             |     7 |    70 |       |            |
|  25 |            CONNECT BY PUMP          |                             |       |       |       |            |
|* 26 |           INDEX RANGE SCAN          | T_COMPANY_ORGAN_IDX_002     |     7 |    70 |       |     1   (0)|
|  27 |       TABLE ACCESS BY INDEX ROWID   | T_POLICY_CHANGE             |     1 |    14 |       |     2  (50)|
|* 28 |        INDEX UNIQUE SCAN            | PK_T_POLICY_CHANGE          |     1 |       |       |     1   (0)|
|  29 |      INDEX FAST FULL SCAN           | IDX1_ACCEPT_DATE            |  1136K|    23M|       |   899   (0)|
|  30 |     TABLE ACCESS BY INDEX ROWID     | T_COMPANY_CUSTOMER          |     1 |    90 |       |     2  (50)|
|* 31 |      INDEX UNIQUE SCAN              | PK_T_COMPANY_CUSTOMER       |     1 |       |       |            |
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("TPF"."FEE_TYPE"="C"."TYPE_ID")
   5 - access("TCM"."POLICY_ID"="TPC"."POLICY_ID")
   7 - access("TPF"."ORGAN_ID"="VW_NSO_1"."$nso_col_1")
   8 - access("D"."APPLICANT_ID"="F"."CUSTOMER_ID"(+))
   9 - access("TPF"."POLICY_ID"="D"."POLICY_ID")
  10 - access("TPF"."PAY_MODE"="G"."MODE_ID")
  12 - filter("TPF"."CHANGE_ID" IS NOT NULL AND TO_NUMBER("TPF"."RECEIV_STATUS")=1 AND "TPF"."FEE_STATUS"=1 AND
              "TPF"."PAYMENT_ID" IS NULL)
  15 - access("indexjoin_alias_012".ROWID="indexjoin_alias_011".ROWID)
  19 - filter("T_COMPANY_ORGAN"."ORGAN_ID"='101')
  21 - access("T_COMPANY_ORGAN"."ORGAN_ID"='101')
  26 - access("T_COMPANY_ORGAN"."PARENT_ID"=NULL)
  28 - access("TPC"."CHANGE_ID"="TPF"."CHANGE_ID")
  31 - access("D"."COMPANY_ID"="E"."COMPANY_ID"(+))

55 rows selected

Statistics
----------------------------------------------------------
         21  recursive calls
          0  db block gets
     125082  consistent gets
      21149  physical reads
          0  redo size
       2448  bytes sent via SQL*Net to client
        656  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
         11  rows processed


 


这个SQL要21秒才能跑完,逻辑读12W左右,问我能不能优化。优化这个SQL我只花了1分钟左右的时间,因为太简单了
你们看这个SQL是典型的JOIN,对付这种SQL肯定要让表走索引,但是从执行计划上看有个1千万行的表T_CONTRACT_MASTER走的是全表扫描,
T_POLICY_FEE 这个400W行的表也是走全表扫描,那么它不慢才怪呢,然后SQL的过滤条件有个 in 子查询
       (select
         organ_id
          from t_company_organ
         start with organ_id = '101'
        connect by prior organ_id = parent_id)
从执行计划上看,CBO对这儿子查询进行了unnest,因为通常情况下CBO认为子查询被unnest之后性能好于filter       

于是我让盖尔查询 子查询返回多少行        
select  organ_id
          from t_company_organ
         start with organ_id = '101'
        connect by prior organ_id = parent_id   ---盖尔说它返回1行        

对于子查询,如果它返回数据很少(这里返回1行),那么可以让它走filter, 而且filter基本上是在SQL最后去阶段执行,这样t_policy_fee就可以走索引了
所以我给这个子查询加了个HINT,禁止子查询扩展

select tpc.policy_id,
       tcm.policy_code,
       tpf.organ_id,
       to_char(tpf.insert_time, 'YYYY-MM-DD') As insert_time,
       tpc.change_id,
       d.policy_code,
       e.company_name,
       f.real_name,
       tpf.fee_type,
       sum(tpf.pay_balance) as pay_balance,
       c.actual_type,
       tpc.notice_code,
       d.policy_type,
       g.mode_name as pay_mode
  from t_policy_change    tpc,
       t_contract_master  tcm,
       t_policy_fee       tpf,
       t_fee_type         c,
       t_contract_master  d,
       t_company_customer e,
       t_customer         f,
       t_pay_mode         g
 where tpc.change_id = tpf.change_id
   and tpf.policy_id = d.policy_id
   and tcm.policy_id = tpc.policy_id
   and tpf.receiv_status = '1'  ---这里原来没引号,是开发那SB搞忘了写'',我让盖尔添加上了,不添加上就没法用索引
   and tpf.fee_status = 1
   and tpf.payment_id is null
   and tpf.fee_type = c.type_id
   and tpf.pay_mode = g.mode_id
   and d.company_id = e.company_id(+)
   and d.applicant_id = f.customer_id(+)
   and tpf.organ_id in
       (select /*+ no_unnest */     --此处的HINT后加的
         organ_id
          from t_company_organ
         start with organ_id = '101'
        connect by prior organ_id = parent_id)
 group by tpc.policy_id,
          tpc.change_id,
          tpf.fee_type,
          to_char(tpf.insert_time, 'YYYY-MM-DD'),
          c.actual_type,
          d.policy_code,
          g.mode_name,
          e.company_name,
          f.real_name,
          tpc.notice_code,
          d.policy_type,
          tpf.organ_id,
          tcm.policy_code
 order by change_id, fee_type

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            |  Name                          | Rows  | Bytes |TempSpc| Cost (%CPU)|
--------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                                | 20026 |  4928K|       | 68615  (30)|
|   1 |  SORT GROUP BY                       |                                | 20026 |  4928K|    10M| 28563   (0)|
|*  2 |   FILTER                             |                                |       |       |       |            |
|   3 |    NESTED LOOPS                      |                                | 20026 |  4928K|       | 27812   (0)|
|   4 |     NESTED LOOPS                     |                                | 20026 |  4498K|       | 23807   (0)|
|   5 |      NESTED LOOPS OUTER              |                                | 20026 |  4224K|       | 19802   (0)|
|   6 |       NESTED LOOPS OUTER             |                                | 20026 |  3911K|       | 15797   (0)|
|   7 |        NESTED LOOPS                  |                                | 20026 |  2151K|       | 15796   (0)|
|*  8 |         HASH JOIN                    |                                | 20026 |  1310K|       | 11791   (0)|
|   9 |          INDEX FULL SCAN             | T_FEE_TYPE_IDX_003             |   106 |   636 |       |     1   (0)|
|* 10 |          HASH JOIN                   |                                | 20026 |  1192K|       | 11789   (0)|
|  11 |           TABLE ACCESS FULL          | T_PAY_MODE                     |    25 |   525 |       |     2   (0)|
|* 12 |           TABLE ACCESS BY INDEX ROWID| T_POLICY_FEE                   | 20026 |   782K|       | 11786   (0)|
|* 13 |            INDEX RANGE SCAN          | IDX_POLICY_FEE__RECEIV_STATUS  |  1243K|       |       | 10188   (0)|
|  14 |         TABLE ACCESS BY INDEX ROWID  | T_CONTRACT_MASTER              |     1 |    43 |       |     2  (50)|
|* 15 |          INDEX UNIQUE SCAN           | PK_T_CONTRACT_MASTER           |     1 |       |       |     1   (0)|
|  16 |        TABLE ACCESS BY INDEX ROWID   | T_COMPANY_CUSTOMER             |     1 |    90 |       |     2  (50)|
|* 17 |         INDEX UNIQUE SCAN            | PK_T_COMPANY_CUSTOMER          |     1 |       |       |            |
|  18 |       TABLE ACCESS BY INDEX ROWID    | T_CUSTOMER                     |     1 |    16 |       |     2  (50)|
|* 19 |        INDEX UNIQUE SCAN             | PK_T_CUSTOMER                  |     1 |       |       |     1   (0)|
|  20 |      TABLE ACCESS BY INDEX ROWID     | T_POLICY_CHANGE                |     1 |    14 |       |     2  (50)|
|* 21 |       INDEX UNIQUE SCAN              | PK_T_POLICY_CHANGE             |     1 |       |       |     1   (0)|
|  22 |     TABLE ACCESS BY INDEX ROWID      | T_CONTRACT_MASTER              |     1 |    22 |       |     2  (50)|
|* 23 |      INDEX UNIQUE SCAN               | PK_T_CONTRACT_MASTER           |     1 |       |       |     1   (0)|
|* 24 |    FILTER                            |                                |       |       |       |            |
|* 25 |     CONNECT BY WITH FILTERING        |                                |       |       |       |            |
|  26 |      NESTED LOOPS                    |                                |       |       |       |            |
|* 27 |       INDEX UNIQUE SCAN              | PK_T_COMPANY_ORGAN             |     1 |     6 |       |            |
|  28 |       TABLE ACCESS BY USER ROWID     | T_COMPANY_ORGAN                |       |       |       |            |
|  29 |      NESTED LOOPS                    |                                |       |       |       |            |
|  30 |       BUFFER SORT                    |                                |     7 |    70 |       |            |
|  31 |        CONNECT BY PUMP               |                                |       |       |       |            |
|* 32 |       INDEX RANGE SCAN               | T_COMPANY_ORGAN_IDX_002        |     7 |    70 |       |     1   (0)|
--------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "T_COMPANY_ORGAN" "T_COMPANY_ORGAN" WHERE
              "T_COMPANY_ORGAN"."PARENT_ID"=NULL AND ("T_COMPANY_ORGAN"."ORGAN_ID"=:B1)))
   8 - access("SYS_ALIAS_1"."FEE_TYPE"="C"."TYPE_ID")
  10 - access("SYS_ALIAS_1"."PAY_MODE"="G"."MODE_ID")
  12 - filter("SYS_ALIAS_1"."CHANGE_ID" IS NOT NULL AND "SYS_ALIAS_1"."FEE_STATUS"=1 AND "SYS_ALIAS_1"."PAYMENT_ID"
              IS NULL)
  13 - access("SYS_ALIAS_1"."RECEIV_STATUS"='1')
  15 - access("SYS_ALIAS_1"."POLICY_ID"="D"."POLICY_ID")
  17 - access("D"."COMPANY_ID"="E"."COMPANY_ID"(+))
  19 - access("D"."APPLICANT_ID"="F"."CUSTOMER_ID"(+))
  21 - access("TPC"."CHANGE_ID"="SYS_ALIAS_1"."CHANGE_ID")
  23 - access("TCM"."POLICY_ID"="TPC"."POLICY_ID")
  24 - filter("T_COMPANY_ORGAN"."ORGAN_ID"=:B1)
  25 - filter("T_COMPANY_ORGAN"."ORGAN_ID"='101')
  27 - access("T_COMPANY_ORGAN"."ORGAN_ID"='101')
  32 - access("T_COMPANY_ORGAN"."PARENT_ID"=NULL)

58 rows selected.

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       2817  consistent gets
          0  physical reads
          0  redo size
       2268  bytes sent via SQL*Net to client
        656  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         40  sorts (memory)
          0  sorts (disk)
          9  rows processed


最终这个SQL能在1秒以内跑完,逻辑读下降到2817 ,到此我就没继续优化了,这个时候停止优化吧,别的了强迫优化症
这个优化案例很简单,我都不好意思贴在博客上,通过这个文章你要学到的就是,如果子查询返回数据很少,那么不妨让它走filter        

原文地址:https://www.cnblogs.com/hehe520/p/6330557.html