對自定義函式使用不當的調優案例

bfc99發表於2015-12-10

一、問題。
    下面這條SQL的執行時間較長,不滿足業務需求。
    select rs.managecom,
       (select codename
          from ldcode
         where codetype = 'station'
           and code = rs.managecom),
       rs.insurername,
       rs.contplancode,
       case
         when sum(prem) - sum(prem2) + sum(prem3) = 0 then
          ''
         else
          case
            when substr(round((sum(rs.pay) + sum(rs.pay2)) /
                              (sum(prem) - sum(prem2) + sum(prem3)),
                              4) * 100,
                        0,
                        1) = '.' then
             '0' || round((sum(rs.pay) + sum(rs.pay2)) /
                          (sum(prem) - sum(prem2) + sum(prem3)),
                          4) * 100 || '%'
            else
             round((sum(rs.pay) + sum(rs.pay2)) /
                   (sum(prem) - sum(prem2) + sum(prem3)),
                   4) * 100 || '%'
          end
       end,
       sum(rs.pay),
       sum(rs.pay2),
       round(sum(prem), 2) - round(sum(prem2), 2) + round(sum(prem3), 2),
       round(sum(prem), 2),
       round(sum(prem2), 2),
       round(sum(prem3), 2)
  from (select g.managecom,
               g.insurername,
               (SELECT LN.CONTPLANNAME
                  FROM LCContPlan LN
                 WHERE LN.CONTPLANCODE = d.CONTPLANCODE
                   AND LN.GrpContno = d.GrpContno
                   AND ROWNUM = 1) contplancode,
               d.insuredno,
               nvl((select sum(a.realpay)
                     from llclaimdetail a, llclaim b
                    where a.clmno = b.clmno
                      and a.grpcontno = c.grpcontno
                      and a.customerno = d.insuredno
                      and a.givetype != '1'
                      and b.clmstate = '60'
                      and b.endcasedate >= date
                    '2015-01-01'
                      and b.endcasedate <= date '2015-11-30'),
                   0) pay,
               nvl((select sum(d.realpay)
                     from LLClaimPolicyBak d, llclaim b
                    where d.grpcontno = c.grpcontno
                      and d.clmno = b.clmno
                      and d.insuredno = c.insuredno
                      and (d.clmstate not in ('60', '70', '100') or
                          d.clmstate is null)
                      and (b.endcasedate is null or b.endcasedate > date
                           '2015-11-30')
                      and d.makedate <= date
                    '2015-11-30'
                      and d.seqno =
                          (select max(seqno)
                             from LLClaimPolicyBak dd, llclaim bb
                            where dd.grpcontno = c.grpcontno
                              and dd.clmno = d.clmno
                              and dd.clmno = bb.clmno
                              and d.riskcode = dd.riskcode
                              and not exists
                            (select 'X'
                                     from llclaimdetailb
                                    where llclaimdetailb.clmno = dd.clmno
                                      and llclaimdetailb.riskcode =
                                          dd.riskcode
                                      and llclaimdetailb.makedate =
                                          dd.makedate)
                              and dd.makedate <= date
                            '2015-11-30'
                              and (dd.clmstate not in ('60', '70', '100') or
                                  dd.clmstate is null)
                              and (bb.endcasedate is null or
                                  bb.endcasedate > date '2015-11-30'))),
                   0) -
               nvl((select sum(d.realpay)
                     from LLClaimPolicyBak d, llclaim b
                    where d.grpcontno = c.grpcontno
                      and d.clmno = b.clmno
                      and d.insuredno = c.insuredno
                      and (d.clmstate not in ('60', '70', '100') or
                          d.clmstate is null)
                      and (b.endcasedate is null or b.endcasedate > date
                           '2015-01-01' - 1)
                      and d.makedate <= date
                    '2015-01-01' - 1
                      and d.seqno =
                          (select max(seqno)
                             from LLClaimPolicyBak dd, llclaim bb
                            where dd.grpcontno = c.grpcontno
                              and dd.clmno = d.clmno
                              and dd.clmno = bb.clmno
                              and not exists
                            (select 'X'
                                     from llclaimdetailb
                                    where llclaimdetailb.clmno = dd.clmno
                                      and llclaimdetailb.riskcode =
                                          dd.riskcode
                                      and llclaimdetailb.makedate =
                                          dd.makedate)
                              and d.riskcode = dd.riskcode
                              and dd.makedate <= date
                            '2015-01-01' - 1
                              and (dd.clmstate not in ('60', '70', '100') or
                                  dd.clmstate is null)
                              and (bb.endcasedate is null or
                                  bb.endcasedate > date '2015-01-01' - 1))),
                   0) pay2,
               splitstr_jzy(getSumPlanPrem(c.grpcontno,
                                           d.insuredno,
                                           date '2015-01-01',
                                           date '2015-11-30'),
                            1) prem,
               splitstr_jzy(getSumPlanPrem(c.grpcontno,
                                           d.insuredno,
                                           date '2015-01-01',
                                           date '2015-11-30'),
                            2) prem2,
               splitstr_jzy(getSumPlanPrem(c.grpcontno,
                                           d.insuredno,
                                           date '2015-01-01',
                                           date '2015-11-30'),
                            3) prem3
          from lccont c, lcinsured d, lcgrpcont g
         where c.grpcontno = d.grpcontno
           and c.contno = d.contno
           and c.insuredno = d.insuredno
           and c.grpcontno = g.grpcontno
           and g.appflag = '1'
           and ((g.cvalidate >= date '2015-01-01' and g.cvalidate <= date
                '2015-11-30') or
               (g.insurprotocoldate >= date
                '2015-01-01' and g.insurprotocoldate <= date '2015-11-30') or
               (g.cvalidate <= date
                '2015-01-01' and g.insurprotocoldate >= date '2015-11-30'))
           and c.managecom like '86%'
           and g.conttype = '2') rs
group by rs.managecom, rs.insurername, rs.contplancode;


以下為該SQL的執行計劃和統計資訊:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------                               
| Id  | Operation                                | Name                           | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |                               
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------                               
|   0 | SELECT STATEMENT                         |                                |      1 |        |     13 |00:03:41.72 |      21M|      6 |       |       |          |                               
|   1 |  TABLE ACCESS BY INDEX ROWID             | LDCODE                         |      2 |      1 |      2 |00:00:00.01 |       6 |      0 |       |       |          |                               
|*  2 |   INDEX UNIQUE SCAN                      | PK_LDCODE                      |      2 |      1 |      2 |00:00:00.01 |       4 |      0 |       |       |          |                               
|*  3 |  COUNT STOPKEY                           |                                |   4800 |        |   4800 |00:00:00.09 |    9600 |      0 |       |       |          |                               
|   4 |   TABLE ACCESS BY INDEX ROWID            | LCCONTPLAN                     |   4800 |      1 |   4800 |00:00:00.08 |    9600 |      0 |       |       |          |                               
|*  5 |    INDEX RANGE SCAN                      | PK_LCCONTPLAN                  |   4800 |      1 |   4800 |00:00:00.05 |    4800 |      0 |       |       |          |                               
|   6 |  SORT AGGREGATE                          |                                |  60166 |      1 |  60166 |00:00:02.22 |     140K|      1 |       |       |          |                               
|   7 |   NESTED LOOPS                           |                                |  60166 |      1 |  18122 |00:00:02.02 |     140K|      1 |       |       |          |                               
|*  8 |    TABLE ACCESS BY INDEX ROWID           | LLCLAIMDETAIL                  |  60166 |      1 |  24885 |00:00:01.44 |   90493 |      1 |       |       |          |                               
|*  9 |     INDEX RANGE SCAN                     | IDX_LLCLAIMDETAIL_CSTMNO       |  60166 |      2 |  35213 |00:00:00.83 |   60776 |      1 |       |       |          |                               
|* 10 |    TABLE ACCESS BY INDEX ROWID           | LLCLAIM                        |  24885 |      1 |  18122 |00:00:00.48 |   49777 |      0 |       |       |          |                               
|* 11 |     INDEX UNIQUE SCAN                    | PK_LLCLAIM                     |  24885 |      1 |  24885 |00:00:00.20 |   24889 |      0 |       |       |          |                               
|  12 |     SORT AGGREGATE                       |                                |  60166 |      1 |  60166 |00:00:09.15 |    1313K|      5 |       |       |          |                               
|* 13 |      FILTER                              |                                |  60166 |        |   2425 |00:00:09.01 |    1313K|      5 |       |       |          |                               
|  14 |       NESTED LOOPS                       |                                |  60166 |      3 |  16062 |00:00:06.71 |    1120K|      5 |       |       |          |                               
|* 15 |        TABLE ACCESS BY INDEX ROWID       | LLCLAIMPOLICYBAK               |  60166 |      3 |    460K|00:00:03.55 |     199K|      5 |       |       |          |                               
|* 16 |         INDEX RANGE SCAN                 | IDX_LLCLAIMPOLICYBAK_INSUREDNO |  60166 |    117 |    598K|00:00:01.36 |     122K|      1 |       |       |          |                               
|* 17 |        TABLE ACCESS BY INDEX ROWID       | LLCLAIM                        |    460K|      1 |  16062 |00:00:02.58 |     920K|      0 |       |       |          |                               
|* 18 |         INDEX UNIQUE SCAN                | PK_LLCLAIM                     |    460K|      1 |    460K|00:00:01.43 |     460K|      0 |       |       |          |                               
|  19 |       SORT AGGREGATE                     |                                |  12397 |      1 |  12397 |00:00:02.21 |     192K|      0 |       |       |          |                               
|  20 |        NESTED LOOPS ANTI                 |                                |  12397 |      1 |    104K|00:00:02.08 |     192K|      0 |       |       |          |                               
|  21 |         NESTED LOOPS                     |                                |  12397 |      1 |    143K|00:00:01.66 |     142K|      0 |       |       |          |                               
|* 22 |          TABLE ACCESS BY INDEX ROWID     | LLCLAIM                        |  12397 |      1 |  12397 |00:00:00.09 |   24796 |      0 |       |       |          |                               
|* 23 |           INDEX UNIQUE SCAN              | PK_LLCLAIM                     |  12397 |      1 |  12397 |00:00:00.05 |   12399 |      0 |       |       |          |                               
|* 24 |          TABLE ACCESS BY INDEX ROWID     | LLCLAIMPOLICYBAK               |  12397 |      1 |    143K|00:00:01.55 |     117K|      0 |       |       |          |                               
|* 25 |           INDEX RANGE SCAN               | IDX_LLCLAIMPOLICYBAK_CLMNO     |  12397 |      1 |   1110K|00:00:00.11 |   29945 |      0 |       |       |          |                               
|* 26 |         TABLE ACCESS BY INDEX ROWID      | LLCLAIMDETAILB                 |  16011 |      1 |   2839 |00:00:00.28 |   50149 |      0 |       |       |          |                               
|* 27 |          INDEX RANGE SCAN                | PK_LLCLAIMDETAILB              |  16011 |      2 |  50957 |00:00:00.15 |   32849 |      0 |       |       |          |                               
|  28 |          SORT AGGREGATE                  |                                |  60166 |      1 |  60166 |00:00:01.40 |     199K|      0 |       |       |          |                               
|* 29 |           FILTER                         |                                |  60166 |        |      0 |00:00:01.29 |     199K|      0 |       |       |          |                               
|  30 |            NESTED LOOPS                  |                                |  60166 |      1 |      0 |00:00:01.24 |     199K|      0 |       |       |          |                               
|* 31 |             TABLE ACCESS BY INDEX ROWID  | LLCLAIMPOLICYBAK               |  60166 |      1 |      0 |00:00:01.19 |     199K|      0 |       |       |          |                               
|* 32 |              INDEX RANGE SCAN            | IDX_LLCLAIMPOLICYBAK_INSUREDNO |  60166 |    117 |    598K|00:00:00.39 |     122K|      0 |       |       |          |                               
|* 33 |             TABLE ACCESS BY INDEX ROWID  | LLCLAIM                        |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|* 34 |              INDEX UNIQUE SCAN           | PK_LLCLAIM                     |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|  35 |            SORT AGGREGATE                |                                |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|  36 |             NESTED LOOPS ANTI            |                                |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|  37 |              NESTED LOOPS                |                                |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|* 38 |               TABLE ACCESS BY INDEX ROWID| LLCLAIM                        |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|* 39 |                INDEX UNIQUE SCAN         | PK_LLCLAIM                     |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|* 40 |               TABLE ACCESS BY INDEX ROWID| LLCLAIMPOLICYBAK               |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|* 41 |                INDEX RANGE SCAN          | IDX_LLCLAIMPOLICYBAK_CLMNO     |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|* 42 |              TABLE ACCESS BY INDEX ROWID | LLCLAIMDETAILB                 |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|* 43 |               INDEX RANGE SCAN           | PK_LLCLAIMDETAILB              |      0 |      2 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               
|  44 |  HASH GROUP BY                           |                                |      1 |      5 |     13 |00:03:41.72 |      21M|      6 |   728K|   728K|  989K (0)|                               
|  45 |   VIEW                                   |                                |      1 |   2233 |  60166 |00:03:41.17 |      21M|      6 |       |       |          |                               
|* 46 |    HASH JOIN                             |                                |      1 |   2233 |  60166 |00:00:03.30 |    7461 |      0 |   820K|   820K| 1183K (0)|                               
|* 47 |     TABLE ACCESS FULL                    | LCGRPCONT                      |      1 |     48 |     48 |00:00:00.01 |      16 |      0 |       |       |          |                               
|* 48 |     HASH JOIN                            |                                |      1 |   2256 |  65410 |00:00:03.14 |    7445 |      0 |  5933K|  2261K| 6542K (0)|                               
|* 49 |      TABLE ACCESS FULL                   | LCCONT                         |      1 |  49349 |  65410 |00:00:02.71 |    4163 |      0 |       |       |          |                               
|  50 |      TABLE ACCESS FULL                   | LCINSURED                      |      1 |  65400 |  65410 |00:00:00.07 |    3282 |      0 |       |       |          |                               
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------                               
                                                                                                                                                                                                        
Predicate Information (identified by operation id):                                                                                                                                                     
---------------------------------------------------                                                                                                                                                     
                                                                                                                                                                                                        
   2 - access("CODETYPE"='station' AND "CODE"=:B1)                                                                                                                                                      
   3 - filter(ROWNUM=1)                                                                                                                                                                                 
   5 - access("LN"."GRPCONTNO"=:B1 AND "LN"."CONTPLANCODE"=:B2)                                                                                                                                         
   8 - filter(("A"."GRPCONTNO"=:B1 AND "A"."GIVETYPE"<>'1'))                                                                                                                                            
   9 - access("A"."CUSTOMERNO"=:B1)                                                                                                                                                                     
  10 - filter(("B"."ENDCASEDATE">=TO_DATE(' 2015-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND "B"."CLMSTATE"='60' AND "B"."ENDCASEDATE"<=TO_DATE(' 2015-11-30                                         
              00:00:00', 'syyyy-mm-dd hh24:mi:ss')))                                                                                                                                                    
  11 - access("A"."CLMNO"="B"."CLMNO")                                                                                                                                                                  
  13 - filter("D"."SEQNO"=)                                                                                                                                                                             
  15 - filter(("D"."GRPCONTNO"=:B1 AND "D"."MAKEDATE"<=TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND ("D"."CLMSTATE" IS NULL OR                                                         
              ("D"."CLMSTATE"<>'60' AND "D"."CLMSTATE"<>'70' AND "D"."CLMSTATE"<>'100'))))                                                                                                              
  16 - access("D"."INSUREDNO"=:B1)                                                                                                                                                                      
  17 - filter(("B"."ENDCASEDATE" IS NULL OR "B"."ENDCASEDATE">TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))                                                                               
  18 - access("D"."CLMNO"="B"."CLMNO")                                                                                                                                                                  
  22 - filter(("BB"."ENDCASEDATE" IS NULL OR "BB"."ENDCASEDATE">TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))                                                                             
  23 - access("BB"."CLMNO"=:B1)                                                                                                                                                                         
  24 - filter(("DD"."GRPCONTNO"=:B1 AND "DD"."RISKCODE"=:B2 AND "DD"."MAKEDATE"<=TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND ("DD"."CLMSTATE"                                         
              IS NULL OR ("DD"."CLMSTATE"<>'60' AND "DD"."CLMSTATE"<>'70' AND "DD"."CLMSTATE"<>'100'))))                                                                                                
  25 - access("DD"."CLMNO"="BB"."CLMNO")                                                                                                                                                                
       filter("DD"."CLMNO"=:B1)                                                                                                                                                                         
  26 - filter(("LLCLAIMDETAILB"."RISKCODE"=:B1 AND "LLCLAIMDETAILB"."MAKEDATE"<=TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND                                                           
              "LLCLAIMDETAILB"."RISKCODE"="DD"."RISKCODE" AND "LLCLAIMDETAILB"."MAKEDATE"="DD"."MAKEDATE"))                                                                                             
  27 - access("LLCLAIMDETAILB"."CLMNO"=:B1)                                                                                                                                                             
       filter("LLCLAIMDETAILB"."CLMNO"="DD"."CLMNO")                                                                                                                                                    
  29 - filter("D"."SEQNO"=)                                                                                                                                                                             
  31 - filter(("D"."MAKEDATE"<=TO_DATE(' 2014-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND "D"."GRPCONTNO"=:B1 AND ("D"."CLMSTATE" IS NULL OR                                                         
              ("D"."CLMSTATE"<>'60' AND "D"."CLMSTATE"<>'70' AND "D"."CLMSTATE"<>'100'))))                                                                                                              
  32 - access("D"."INSUREDNO"=:B1)                                                                                                                                                                      
  33 - filter(("B"."ENDCASEDATE">TO_DATE(' 2014-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') OR "B"."ENDCASEDATE" IS NULL))                                                                               
  34 - access("D"."CLMNO"="B"."CLMNO")                                                                                                                                                                  
  38 - filter(("BB"."ENDCASEDATE">TO_DATE(' 2014-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') OR "BB"."ENDCASEDATE" IS NULL))                                                                             
  39 - access("BB"."CLMNO"=:B1)                                                                                                                                                                         
  40 - filter(("DD"."MAKEDATE"<=TO_DATE(' 2014-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND "DD"."GRPCONTNO"=:B1 AND "DD"."RISKCODE"=:B2 AND ("DD"."CLMSTATE"                                         
              IS NULL OR ("DD"."CLMSTATE"<>'60' AND "DD"."CLMSTATE"<>'70' AND "DD"."CLMSTATE"<>'100'))))                                                                                                
  41 - access("DD"."CLMNO"="BB"."CLMNO")                                                                                                                                                                
       filter("DD"."CLMNO"=:B1)                                                                                                                                                                         
  42 - filter(("LLCLAIMDETAILB"."RISKCODE"=:B1 AND "LLCLAIMDETAILB"."MAKEDATE"<=TO_DATE(' 2014-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND                                                           
              "LLCLAIMDETAILB"."RISKCODE"="DD"."RISKCODE" AND "LLCLAIMDETAILB"."MAKEDATE"="DD"."MAKEDATE"))                                                                                             
  43 - access("LLCLAIMDETAILB"."CLMNO"=:B1)                                                                                                                                                             
       filter("LLCLAIMDETAILB"."CLMNO"="DD"."CLMNO")                                                                                                                                                    
  46 - access("C"."GRPCONTNO"="G"."GRPCONTNO")                                                                                                                                                          
  47 - filter(("G"."CONTTYPE"='2' AND "G"."APPFLAG"='1' AND (("G"."INSURPROTOCOLDATE">=TO_DATE(' 2015-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND                                                    
              "G"."INSURPROTOCOLDATE"<=TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss')) OR ("G"."CVALIDATE">=TO_DATE(' 2015-01-01 00:00:00', 'syyyy-mm-dd                                     
              hh24:mi:ss') AND "G"."CVALIDATE"<=TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss')) OR ("G"."INSURPROTOCOLDATE">=TO_DATE(' 2015-11-30 00:00:00',                                 
              'syyyy-mm-dd hh24:mi:ss') AND "G"."CVALIDATE"<=TO_DATE(' 2015-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))))                                                                              
  48 - access("C"."GRPCONTNO"="D"."GRPCONTNO" AND "C"."CONTNO"="D"."CONTNO" AND "C"."INSUREDNO"="D"."INSUREDNO")                                                                                        
  49 - filter("C"."MANAGECOM" LIKE '86%')                                                                                                                                                               




Statistics
----------------------------------------------------------
    1944733  recursive calls
    4331962  db block gets
   17399665  consistent gets
          2  physical reads
        116  redo size
       2735  bytes sent via SQL*Net to client
       5649  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
     180506  sorts (memory)
          0  sorts (disk)
         13  rows processed
         
二,處理過程。
    從統計資訊上看,為了完成最終只有13行(13  rows processed)的一個查詢,邏輯讀17399665個塊次(17399665  consistent gets),以資料庫塊8K大小的預設值計算的話,意味著前後一共處理了130G左右資料。遞迴呼叫也很高(1944733  recursive calls)。
    遞迴呼叫也是一種比較消耗資源的操作,當發生空間不足,需要更多的分割槽(extent)時;引起觸發器工作時;執行儲存過程、自定義函式等時;需要從磁碟讀取資料字典資訊時等情況下,都會引起遞迴呼叫。也可以簡單理解為需要執行本SQL之外的其它SQL時,就稱之為遞迴呼叫。
    再從執行計劃上看,注意到starts列上有不少成千上萬的值,甚至還有460K的。而該列是表示相應物件被訪問的次數的。如果相應的物件的訪問方法還是全表掃描或是索引全掃描,那就更需要引起注意。
    所以,初步看上去,調優的潛力很大。(返回行少,但資源消耗大的,通常調優的潛力都比較大。)
    在閱讀SQL程式碼時,發現其中有這樣的程式碼:
    splitstr_jzy(getSumPlanPrem(c.grpcontno,
                                           d.insuredno,
                                           date '2015-01-01',
                                           date '2015-11-30')
    顯然,這裡的splitstr_jzy和getSumPlanPrem都是自定義函式。考慮到前邊較高的遞迴呼叫,所以,很可能這種過高的遞迴呼叫是因這兩個自定義函式所產生的。
    這時,第一個想法產生了,即暫時註釋掉這些函式的引用,看看情況會如何。如果效能有明顯提升,說明問題的根源肯定與這些自定義函式相關。我們就要把主要精力先放在這些自定義函式上。如果效能提升不大,那麼說明問題的根源與這些自定義函式關係不大,我們需要繼續分析其它方面的問題。
    為節省往篇幅,這裡只提供與這些自定義函式相關的程式碼:
               /*splitstr_jzy(getSumPlanPrem(c.grpcontno,
                                           d.insuredno,
                                           date '2015-01-01',
                                           date '2015-11-30'),
                            1)*/ 0 prem,
               /*splitstr_jzy(getSumPlanPrem(c.grpcontno,
                                           d.insuredno,
                                           date '2015-01-01',
                                           date '2015-11-30'),
                            2)*/ 0 prem2,
               /*splitstr_jzy(getSumPlanPrem(c.grpcontno,
                                           d.insuredno,
                                           date '2015-01-01',
                                           date '2015-11-30'),
                            3)*/ 0 prem3
          from lccont c, lcinsured d, lcgrpcont g
     從以上程式碼可見,通過註釋掉相關函式,並固定設定一個0值來代替原函式執行的結果。之所以用一個固定值來代替,是因為相關自定義函式的呼叫處於子查詢中,外部查詢需要使用這些函式的結果,如果完全註釋掉,還要修改外部查詢的相關程式碼,動靜比較大。
   再次執行註釋掉相關函式的SQL,統計資訊的結果如下:

     Statistics
----------------------------------------------------------
       2718  recursive calls
          0  db block gets
    1671681  consistent gets
       3255  physical reads
        260  redo size
       2432  bytes sent via SQL*Net to client
       5762  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         99  sorts (memory)
          0  sorts (disk)
         13  rows processed
         
     很明顯,遞迴呼叫減少為約2700次,邏輯讀1671681個塊次,相較於註釋前,分別只是原來的千分之一和十分之一。而且實際的執行時間也只要不到20秒了。看來這兩個函式是我們主攻的方向。
     通過對getSumPlanPrem原始碼的分析,發現其主要是對若干表的單表查詢,並將獲得的指定列的值賦給變數,然後根據對這些變數的比較和計算,最終形成三個數值結果,但返回時,是將三個數值結果轉換為字元,並以逗號分隔拼接後,以一個字串的形式返回。而這些單表的查詢中,有相當一部分的表上並沒有適用的索引(不是沒有索引,而是沒有適合相關查詢的索引。),而使得部分查詢需要訪問更多的資料塊才能得到需要的結果,需要消耗更多的資源。比如:
     select c.prem
          into tPrem
          from lccont c
         where c.grpcontno = cGrpContNo
           and c.insuredno = cInsur edNo;
    這樣一個查詢。表lccont有一個名為PK_LCCONT的主鍵,主鍵列為(CONTNO)。grpcontno列和insuredno列各有一個單列索引,而該表共有約6萬行資料。僅從這條語句的角度來看,無論是走這兩列中的任一個索引,最終都要回表去獲取PREM列的值,不如將grpcontno列、insuredno列和prem列編入一個複合索引中,這樣不僅不用回表,而且這個索引的選擇性會更好。這裡簡單展開一下,對於複合索引,建立時列的順序是很有講究的。要把WHERE條件中出現的列寫到前邊,如果有多個,哪個重複值高,就把哪一列寫到前面(通常而言)。這樣,當某個SQL中只有對第二列條件的過濾時,就有可能用上該複合索引(以索引跳躍掃描的方式來訪問),反之,把重複值低的放到前面,當只有對第二列條件的過濾時,這個複合索引很可能是用不上的。而造成這種狀況的原因是和複合索引的儲存結構相關的。
    再次執行原SQL(未註釋自定義函式程式碼的SQL),統計資訊如下:
    Statistics
----------------------------------------------------------
    1944042  recursive calls
    4331962  db block gets
    4623587  consistent gets
        351  physical reads
        144  redo size
       2735  bytes sent via SQL*Net to client
       5650  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
     180498  sorts (memory)
          0  sorts (disk)
         13  rows processed
    邏輯讀降為4623587個塊次(4623587  consistent gets),只是原來的四分之一了。但由於呼叫自定義函式的次數沒有減少,所以,遞迴呼叫的次數沒有改變。
    再來看這段對自定義函式呼叫的程式碼:
     splitstr_jzy(getSumPlanPrem(c.grpcontno,
                                 d.insuredno,
                                 date '2015-01-01',
                                 date '2015-11-30'),
                            1) prem,
    splitstr_jzy(getSumPlanPrem(c.grpcontno,
                                d.insuredno,
                                date '2015-01-01',
                                date '2015-11-30'),
                            2)0 prem2,
    splitstr_jzy(getSumPlanPrem(c.grpcontno,
                                d.insuredno,
                                date '2015-01-01',
                                date '2015-11-30'),
                            3) prem3
      可以看到對getSumPlanPrem的呼叫重複了三次,且輸入的引數完全一樣。而splitstr_jzy函式的作用就是把以逗號分隔的,包括三個結果值的字串根據第二個輸入引數的值,把第N段的結果拆分出來。
      如果我們這裡把它變成只呼叫一次,那麼該自定義函式的呼叫就會減少三分之二。那麼無論是遞迴呼叫的次數,還是函式執行產生的資源消耗都會減少。
      所以,我們這裡只保留一次呼叫,而這個splitstr_jzy自定義函式的功能,完全可以用正規表示式來實現,即把外部查詢對原prem,prem2和prem3的引用,分別改為regexp_substr(prem_all,'[^,]+',1,1),  regexp_substr(prem_all,'[^,]+',1,2), regexp_substr(prem_all,'[^,]+',1,3)。
      再次修改後的程式碼大致為:
      select ...
                regexp_substr(prem_all,'[^,]+',1,1) prem,
                regexp_substr(prem_all,'[^,]+',1,2) prem2,
                regexp_substr(prem_all,'[^,]+',1,3) prem3,
             ...
      from (
             select ....
                    getSumPlanPrem(c.grpcontno,
                                   d.insuredno,
                                   date '2015-01-01',
                                   date '2015-11-30') prem_all
             from .....
             where .....)
      where ....
      group by .....;
      
      對這樣處理的SQL程式碼再次執行,其統計資訊如下:
      Statistics
----------------------------------------------------------
     527686  recursive calls
          0  db block gets
    2416931  consistent gets
         93  physical reads
        188  redo size
       2917  bytes sent via SQL*Net to client
       6671  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
      60166  sorts (memory)
          0  sorts (disk)
         13  rows processed
      遞迴呼叫的次數變為527686次,邏輯讀的次數為2416931次,相較於上一次改寫,又分別下降了約70%和50%。實際的執行時間也減少到約30秒。
      而此時的執行計劃是:
      ----------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                | Name                           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                         |                                |      1 |        |     13 |00:00:32.02 |    2416K|       |       |          |
|   1 |  TABLE ACCESS BY INDEX ROWID             | LDCODE                         |      2 |      1 |      2 |00:00:00.01 |       6 |       |       |          |
|*  2 |   INDEX UNIQUE SCAN                      | PK_LDCODE                      |      2 |      1 |      2 |00:00:00.01 |       4 |       |       |          |
|*  3 |  COUNT STOPKEY                           |                                |   4800 |        |   4800 |00:00:00.07 |    9600 |       |       |          |
|   4 |   TABLE ACCESS BY INDEX ROWID            | LCCONTPLAN                     |   4800 |      1 |   4800 |00:00:00.05 |    9600 |       |       |          |
|*  5 |    INDEX RANGE SCAN                      | PK_LCCONTPLAN                  |   4800 |      1 |   4800 |00:00:00.03 |    4800 |       |       |          |
|   6 |  SORT AGGREGATE                          |                                |  60166 |      1 |  60166 |00:00:01.30 |     140K|       |       |          |
|   7 |   NESTED LOOPS                           |                                |  60166 |      1 |  18122 |00:00:01.17 |     140K|       |       |          |
|*  8 |    TABLE ACCESS BY INDEX ROWID           | LLCLAIMDETAIL                  |  60166 |      1 |  24808 |00:00:00.82 |   90718 |       |       |          |
|*  9 |     INDEX RANGE SCAN                     | IDX_LLCLAIMDETAIL_CSTMNO       |  60166 |      2 |  35102 |00:00:00.50 |   60780 |       |       |          |
|* 10 |    TABLE ACCESS BY INDEX ROWID           | LLCLAIM                        |  24808 |      1 |  18122 |00:00:00.28 |   49622 |       |       |          |
|* 11 |     INDEX UNIQUE SCAN                    | PK_LLCLAIM                     |  24808 |      1 |  24808 |00:00:00.14 |   24810 |       |       |          |
|  12 |     SORT AGGREGATE                       |                                |  60166 |      1 |  60166 |00:00:07.19 |    1315K|       |       |          |
|* 13 |      FILTER                              |                                |  60166 |        |   2421 |00:00:07.10 |    1315K|       |       |          |
|  14 |       NESTED LOOPS                       |                                |  60166 |      3 |  16062 |00:00:04.91 |    1121K|       |       |          |
|* 15 |        TABLE ACCESS BY INDEX ROWID       | LLCLAIMPOLICYBAK               |  60166 |      3 |    460K|00:00:01.90 |     201K|       |       |          |
|* 16 |         INDEX RANGE SCAN                 | IDX_LLCLAIMPOLICYBAK_INSUREDNO |  60166 |    117 |    610K|00:00:00.57 |     122K|       |       |          |
|* 17 |        TABLE ACCESS BY INDEX ROWID       | LLCLAIM                        |    460K|      1 |  16062 |00:00:02.63 |     920K|       |       |          |
|* 18 |         INDEX UNIQUE SCAN                | PK_LLCLAIM                     |    460K|      1 |    460K|00:00:01.48 |     460K|       |       |          |
|  19 |       SORT AGGREGATE                     |                                |  12397 |      1 |  12397 |00:00:02.10 |     193K|       |       |          |
|  20 |        NESTED LOOPS ANTI                 |                                |  12397 |      1 |    104K|00:00:01.98 |     193K|       |       |          |
|  21 |         NESTED LOOPS                     |                                |  12397 |      1 |    143K|00:00:01.61 |     143K|       |       |          |
|* 22 |          TABLE ACCESS BY INDEX ROWID     | LLCLAIM                        |  12397 |      1 |  12397 |00:00:00.09 |   24796 |       |       |          |
|* 23 |           INDEX UNIQUE SCAN              | PK_LLCLAIM                     |  12397 |      1 |  12397 |00:00:00.05 |   12399 |       |       |          |
|* 24 |          TABLE ACCESS BY INDEX ROWID     | LLCLAIMPOLICYBAK               |  12397 |      1 |    143K|00:00:01.50 |     118K|       |       |          |
|* 25 |           INDEX RANGE SCAN               | IDX_LLCLAIMPOLICYBAK_CLMNO     |  12397 |      1 |   1116K|00:00:00.10 |   29984 |       |       |          |
|* 26 |         TABLE ACCESS BY INDEX ROWID      | LLCLAIMDETAILB                 |  16011 |      1 |   2849 |00:00:00.24 |   50276 |       |       |          |
|* 27 |          INDEX RANGE SCAN                | PK_LLCLAIMDETAILB              |  16011 |      2 |  51475 |00:00:00.13 |   32852 |       |       |          |
|  28 |          SORT AGGREGATE                  |                                |  60166 |      1 |  60166 |00:00:01.29 |     201K|       |       |          |
|* 29 |           FILTER                         |                                |  60166 |        |      0 |00:00:01.20 |     201K|       |       |          |
|  30 |            NESTED LOOPS                  |                                |  60166 |      1 |      0 |00:00:01.16 |     201K|       |       |          |
|* 31 |             TABLE ACCESS BY INDEX ROWID  | LLCLAIMPOLICYBAK               |  60166 |      1 |      0 |00:00:01.11 |     201K|       |       |          |
|* 32 |              INDEX RANGE SCAN            | IDX_LLCLAIMPOLICYBAK_INSUREDNO |  60166 |    117 |    610K|00:00:00.35 |     122K|       |       |          |
|* 33 |             TABLE ACCESS BY INDEX ROWID  | LLCLAIM                        |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|* 34 |              INDEX UNIQUE SCAN           | PK_LLCLAIM                     |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|  35 |            SORT AGGREGATE                |                                |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|  36 |             NESTED LOOPS ANTI            |                                |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|  37 |              NESTED LOOPS                |                                |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|* 38 |               TABLE ACCESS BY INDEX ROWID| LLCLAIM                        |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|* 39 |                INDEX UNIQUE SCAN         | PK_LLCLAIM                     |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|* 40 |               TABLE ACCESS BY INDEX ROWID| LLCLAIMPOLICYBAK               |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|* 41 |                INDEX RANGE SCAN          | IDX_LLCLAIMPOLICYBAK_CLMNO     |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|* 42 |              TABLE ACCESS BY INDEX ROWID | LLCLAIMDETAILB                 |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|* 43 |               INDEX RANGE SCAN           | PK_LLCLAIMDETAILB              |      0 |      2 |      0 |00:00:00.01 |       0 |       |       |          |
|  44 |  HASH GROUP BY                           |                                |      1 |      5 |     13 |00:00:32.02 |    2416K|   728K|   728K|  994K (0)|
|  45 |   VIEW                                   |                                |      1 |   2233 |  60166 |00:00:30.08 |    2416K|       |       |          |
|* 46 |    HASH JOIN                             |                                |      1 |   2233 |  60166 |00:00:03.12 |    7461 |   820K|   820K| 1192K (0)|
|* 47 |     TABLE ACCESS FULL                    | LCGRPCONT                      |      1 |     48 |     48 |00:00:00.01 |      16 |       |       |          |
|* 48 |     HASH JOIN                            |                                |      1 |   2256 |  65410 |00:00:03.08 |    7445 |  5933K|  2261K| 6473K (0)|
|* 49 |      TABLE ACCESS FULL                   | LCCONT                         |      1 |  49349 |  65410 |00:00:02.78 |    4163 |       |       |          |
|  50 |      TABLE ACCESS FULL                   | LCINSURED                      |      1 |  65400 |  65410 |00:00:00.07 |    3282 |       |       |          |
----------------------------------------------------------------------------------------------------------------------------------------------------------------
    而下一步的重點,則要是要消除這些成千上萬次的表連線次數。而結合謂詞資訊的分析,發現這裡大部分的成千上萬次表連線的原因,就是使用了大量的標量子查詢(即只返回一行一列的子查詢)。而這些子查詢又都出現在外部查詢的SELECT部分,這也就意味著,外部查詢返回多少行,這個子查詢就要執行多少次,相關的表就要被訪問多少次。所以,進一步優化的方法,就是改寫SQL,把這些子查詢移到FROM 後面,這樣相關表只要訪問一次就可以。
    由於改寫過程中出現改寫後的結果與原SQL不一致,而使用者又不願提供原始資料供分析,以及擔心SQL改動過大而可能引起的不利因素等原因,所以,進一步的優化沒能進行下去。
   按我本意,是想在將大部分的超高表連線次數消除後,繼續檢查相關表上統計資訊的準確性(從執行計劃中可見,有部分處理步驟中,估算的行數與實際的行數相差較大。),以確保CBO不致因統計資訊的問題而產生錯誤的執行計劃。如果一切順利,這個SQL的執行時間調到秒出是有可能的。
   
三、總結
    通過這個案例,有以下幾點經驗:
    1、當出現超高的遞迴呼叫時,要檢查SQL中是否有自定義函式的呼叫,並嘗試減少這些函式的呼叫,以及優化函式自身。
    2、當有內建函式可以實現的功能時,儘量使用系統內建函式。
    3、當邏輯讀較高,且伴隨有大量的表連線次數(starts列)時,可以通過改寫SQL,達到減少表連線次數,降低邏輯讀的目的。
    
   
   

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/22207394/viewspace-1869816/,如需轉載,請註明出處,否則將追究法律責任。

相關文章