<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    小菜毛毛技術分享

    與大家共同成長

      BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
      164 Posts :: 141 Stories :: 94 Comments :: 0 Trackbacks
    ORACLE SQL TUNING
    一.優化器模式
       ORACLE的優化器共有3種:
       a.  RULE (基于規則)   b. COST (基于成本)  c. CHOOSE (選擇性)
       為了使用基于成本的優化器(CBO, Cost-Based Optimizer) , 你必須定期更新統計信息,以保證數據庫中的對象統計信息(object statistics)的準確性.
       如果數據庫的優化器模式設置為選擇性(CHOOSE),那么實際的優化器模式將和是否運行過analyze命令有關. 如果table已經被analyze過, 優化器模式將自動成為CBO , 反之,數據庫將采用RULE形式的優化器。

    二.訪問Table的方式
    ORACLE 采用兩種訪問表中記錄的方式:
    a.  全表掃描
          全表掃描就是順序地訪問表中每條記錄. ORACLE采用一次讀入多個數 據塊(database block)的方式優化全表掃描。
       
    b.  索引掃描
       你可以采用基于ROWID的訪問方式情況,提高訪問表的效率, ROWID包含了表中記錄的物理位置信息.ORACLE采用索引(INDEX)實現了數據和存放數據的物理位置(ROWID)之間的聯系. 通常索引提供了快速訪問ROWID的方法,因此那些基于索引列的查詢就可以得到性能上的提高.

    其中ORACLE對索引又有兩種訪問模式.
    a)索引唯一掃描 ( INDEX UNIQUE SCAN)
    大多數情況下, 優化器通過WHERE子句訪問INDEX.
    例如:
    表LOADING有兩個索引 : 建立在LOADING列上的唯一性索引LOADING_PK和建立在MANAGER列上的非唯一性索引IDX_MANAGER.
    SELECT loading 
    FROM LOADING
    WHERE LOADING = ‘ROSE HILL’;
       在內部 , 上述SQL將被分成兩步執行, 首先 , LOADING_PK 索引將通過索引唯一掃描的方式被訪問 , 獲得相對應的ROWID, 通過ROWID訪問表的方式執行下一步檢索.
       如果被檢索返回的列包括在INDEX列中,ORACLE將不執行第二步的處理(通過ROWID訪問表). 因為檢索數據保存在索引中, 單單訪問索引就可以完全滿足查詢結果.
       下面SQL只需要INDEX UNIQUE SCAN 操作.
           SELECT LOADING
           FROM  LOADING
    WHERE LOADING = ‘ROSE HILL’;
     
      b)索引范圍查詢(INDEX RANGE SCAN)
         適用于兩種情況:
    1. 基于一個范圍的檢索
    2. 基于非唯一性索引的檢索
     例1:
          SELECT LOADING
          FROM  LOADING
    WHERE LOADING LIKE ‘M%’;
     
    WHERE子句條件包括一系列值, ORACLE將通過索引范圍查詢的方式查詢LODGING_PK . 由于索引范圍查詢將返回一組值, 它的效率就要比索引唯一掃描
    低一些. 
    例2:
          SELECT LOADING
          FROM  LOADING
    WHERE MANAGER = ‘BILL GATES’;
     這個SQL的執行分兩步, IDX_MANAGER的索引范圍查詢(得到所有符合條件記錄的ROWID) 和下一步同過ROWID訪問表得到LOADING列的值. 由于IDX_MANAGER是一個非唯一性的索引,數據庫不能對它執行索引唯一掃描.
     
      由于SQL返回LOADING列,而它并不存在于IDX_MANAGER索引中, 所以在索引范圍查詢后會執行一個通過ROWID訪問表的操作.
      WHERE子句中, 如果索引列所對應的值的第一個字符由通配符(WILDCARD)開始, 索引將不被采用.
    SELECT LOADING
          FROM  LOADING
    WHERE MANAGER LIKE ‘%HANMAN’;
    在這種情況下,ORACLE將使用全表掃描.


    三.SQL調優的本質就是調整執行計劃。
     在好多情況下,oracle自動選擇的執行計劃并不是最優的,這時需要我們人工去干預。(什么是執行計劃?)
     

    對SQL調優基本步驟:
    a) 捕獲SQL語句
    b) 產生SQL語句的執行計劃;
    c) 驗證統計信息(SQL語句涉及到的表格是否做過分析),表格信息(結果集的記錄數,索引),字段上面數據分布特點
    d) 通過手工收集到的信息,形成自己理想的執行計劃。
    e) 如果做過分析,則重新分析相關表格或者做柱狀圖分析。
    f) 如果沒有做過分析,則通過嘗試不同的Hint,從而獲得合適的執行計劃。
    g) 當我們正常無法調優到位時,可以打開10053事件打開優化器的跟蹤,看看Oracle如何選擇的.
    alter session set events='10053 trace name context forever,level 2';
     
    四.如何捕獲SQL語句
     捕獲SQL語句的方法有如下幾種:
      1.SQL TRACE或10046跟蹤某個模塊。
      2.PERFSTAT性能統計包,使用方法見附錄二。
      3.V$SQL,V$SESSION_WAIT,V$SQL_TEXT
    五.如何查看執行計劃
     查看SQL語句的執行計劃有以下幾種:
     1.Set autotrace on(set autotrace traceonly exp)
     2.Explain plan for …..
      @?/rdbms/admin/utlxpls.sql
     3.V$SQL_PLAN視圖
      column operation format a16
    column "Query Plan" format a60
    column options format a15
    column object_name  format a20
    column id  format 99

    select id,lpad(' ',2*(level-1))||operation||' '||options||' '||object_name||' '
           ||decode(id,0,'Cost = '||position) "Query Plan"
    from (select *
    from v$sql_plan 
    where address='&a') sql_plan
    start with id = 0
    connect by prior id = parent_id
    /

     4.第三方工具,如pl/sql developer,TOAD
     
    六.SQL語句主要的連接方法

    a) Nested-loop join
    適合于小表(幾千條,幾萬條記錄)與大表做聯接
    在聯接列上有索引。

     分內表和外表(驅動表),靠近from子句的是內表。從效率上講,小表應該作外表,大表應該作內表,即大表查詢時走索引。

    COST= Access cost of A(驅動表) + (access cost of B * number of rows from A)

    成本計算方法:
     設小表100行,大表100000行。

     兩表均有索引:
     如果小表在內,大表在外(驅動表)的話,則掃描次數為:
      100000+100000*2 (其中2表示IO次數,一次索引,一次數據)
     如果大表在內,小表在外(驅動表)的話,則掃描次數為:
      100+100*2.

     兩表均無索引:
     如果小表在內,大表在外的話,則掃描次數為:
      100000+100*100000
     如果大表在內,小表在外的話,則掃描次數為:
      100+100000*100

    注意:如果一個表有索引,一個表沒有索引,ORACLE會將沒有索引的表作驅動表。如果兩個表都有索引,則外表作驅動表。如果兩個都沒索引的話,則也是外表作驅動表。

     基本的執行計劃如下所示:
      NESTED LOOPS
               TABLE ACCESS (BY ROWID)  OF  our_outer_table
                       INDEX (..SCAN) OF outer_table_index(….)
               TABLE ACCESS (BY ROWID)  OF  our_inner_table
                 INDEX (..SCAN) OF inner_table_index(….)

    b) Hash join

    適合于大表與大表,小表(幾十萬,幾百萬)與大表之間的聯連。
    聯接列上不需要索引。

    基本執行計劃如下:
     HASH JOIN
                  TABLE ACCESS (….)  OF  tableA
                  TABLE ACCESS (….)  OF  tableB

    cost= (access cost of A * number of hash partitions of B) + access cost of B

    可以看出主要成本在于A表是否可以被Cache。Hash_area_size的大小將決定Hash Join的主要成本??梢钥闯鯤ash Join的成本和返回集合并沒有直接的關系,所以當返回結果集比較大的時候一般具有較好的性能。

    為了加快hash join的速度,可以調大hash_area_size和pga_aggregate_target(默認為25M)的值。


    c) Sort Merge join

    每一個Row Source在Join列上均排序。
     然后兩個排序后的Row Source合并后,作一個結果集返回。
     Sort/Merge Join僅僅對equal Join有效。

    基本執行計劃
     MERGE (JOIN)
            SORT (JOIN)
                     TABLE ACCESS (….)  OF  tableA
            SORT (JOIN)
                     TABLE ACCESS (….)  OF  tableB

    cost= access cost of A + access cost of B +(sort cost of A + sort cost of B)

    可以看出Sort的成本是Merge Join的主要構成部分。這樣sort_area_size的大小將很大程度決定Merge Join的大小。同樣如果A表或者B表已經經過排序的,那么Merge Join往往具有很好的性能。其不會走索引。

    沒有驅動表的概念,即時響應能力較差。

     

    七.一般情況下最常見的5種問題

    1. Statement not written for indexes 25%
    2. Indexes are missing or inappropriate 16%
    3. Use of single-column index merge 15%
    4. Misuse of nested loop, sort merge, or hash join 12%
    5. Misuse of IN, EXISTS, NOT IN, NOT EXISTS, or table joins 8%
     
     不過在我們這里,最常見的問題是在第2條,第3條,第4條。

    1. Statement not written for indexes
    類似于這樣的:
    SELECT account_name, trans_date, amount
    FROM transaction
    WHERE SUBSTR(account_name,1,7) = ' CAPITAL';

    WHERE account_name LIKE 'CAPITAL%';

    Account_date 日期

    To_char(Account_date,’YYYY-MM-DD:HH24:MI:SS’)=’200508XXX’;

    Account_date=to_date(‘200508….’,’yyyy-mm-dd);


    2.Indexes are missing or inappropriate
     
     例如REP_C021中有這樣一句:
    select SUBSIDIARYID,260,'    300電話卡',
        sum(decode(feetype, 1, ceil(duration / 60))) +
             sum(decode(feetype, 0, ceil(duration / 60))),
             sum(decode(feetype, 1, ceil(duration / 60))),
             sum(decode(feetype, 0, ceil(duration / 60))),0
        from cardsusage200508 a, service b
       where a.caller = b.servicecode and
             (b.property = i_property or i_property is null) and
             a.cdrtype = 102
       group by SUBSIDIARYID, 260, '    300電話卡';

    Execution Plan
    ----------------------------------------------------------
       0      SELECT STATEMENT Optimizer=RULE
       1    0   SORT (GROUP BY)
       2    1     NESTED LOOPS
       3    2       TABLE ACCESS (FULL) OF 'CARDSUSAGE200508'
       4    2       TABLE ACCESS (BY INDEX ROWID) OF 'SERVICE'
       5    4         INDEX (UNIQUE SCAN) OF 'SERVICE_CODE'
     
    我們取其中的select語句進行調優。在調整之前,原select語句需要6分鐘左右。

    12:19:20 SQL> select cdrtype,count(*) from cardsusage200508
    12:20:12   2  group by cdrtype;

    CDRT   COUNT(*)
    ---- ----------
    102         637
    106     1973757
    107     2390097
    112       46016
    113          20

    針對cardsuage200508表格的特性,我們在CDRTYPE字段上建立一個位圖索引CARDSUSAGE_CDRTYPE_BTIDX。
    將SQL語句加上以下Hint:
      select /*+  INDEX(A, CARDSUSAGE_CDRTYPE_BTIDX)*/
             SUBSIDIARYID,260,'    300電話卡',
             sum(decode(feetype, 1, ceil(duration / 60))) +
             sum(decode(feetype, 0, ceil(duration / 60))),
             sum(decode(feetype, 1, ceil(duration / 60))),
             sum(decode(feetype, 0, ceil(duration / 60))),0
        from cardsusage200508  a, service b
       where a.caller = b.servicecode and
             (b.property = i_property or i_property is null) and
             a.cdrtype = 102
       group by SUBSIDIARYID, 260, '    300電話卡';
     這樣調整后,只需要幾秒鐘即可出來。

    3.  Use of single-column index merge
     復合索引有的時候比單列索引效率更高。根據where子句中的具體情況,有 時可以建立復合索引。例如:
     select a.AccountNum,a.ChargeID,a.Total,b.ItemID,
          b.Amount,c.billingcycle
      from charge_bill a, chargedetail_bill b, Account c
     where a.AccountNum > 1 and a.AccountNum <= 1969618 and
           a.status = '0' and a.InvoiceID is null and c.paymentmethod != '7' and
           a.Total > 0 and a.AccountNum = c.AccountNum and
           a.ChargeID = b.ChargeID
     order by a.AccountNum, a.ChargeID, b.ItemID;
    這樣的SQL語句執行需要3分27秒。

    我們做了以下優化:
    在charge_bill表格的accountnum,status,total,invoiceid列上建立一個復合索引。這樣上述SQL語句需要40秒左右。

     Resume Service過程中有這么一句:
     SELECT NVL(SUM(A.FEE),0)  
    FROM ACCOUNTBALANCE A,INVOICE B 
    WHERE A.OBJECTID = B.INVOICEID  AND A.ACCOUNTNUM = :b1
    AND B.BILLINGBEGINDATE < TO_DATE(:b2,'yyyymmdd');
    該語句需要執行大概72000次。整個過程執行大概需要100分鐘左右。

    將:b1以具體的值代替,這條SQL語句執行很快,大概0.1秒左右。

    我們做了以下優化:
    在invoiceid,billingbegindate列上創建了一個索引idx_invoice_hc。
    將上述SQL語句改成:
    select /*+ use_nl(a,b) index(b,IDX_INVOICE_HC)*/  nvl(sum(a.fee),0)
    from accountbalance a,invoice b
    where a.objectid=b.invoiceid  and a.accountnum=m_accountnum
    and b.billingbegindate

    這樣一來,該過程的執行時間快的時候大概在10分鐘左右,慢的時候(IO異常緊張的時)大概在30分鐘左右。


    4. Misuse of nested loop, sort merge, or hash join
     表格之間的連接方式和連接順序都將極大的影響SQL語句的性能。這種問  題在平時最常見。ORACLE在處理5張或5張以上的表格的連接時候,很容 易出問題。一般情況下,謹記前面表格之間的連接原則,即可以處理此類問 題。
     
       例如:
      select b.SUBSIDIARYID,
           c.paymentmethod || ':' || nvl(subscribertype, '9999999'),
           'gsm',count(*),sum(decode(untelLOCALCHARGE,
                      0,decode(duration,0,1,
                             decode(sign(duration - 1800),
                                    1, 2 + trunc((duration - 1201) / 600),
                                    2)), trunc((duration + 599) / 600))),
           sum(nvl(GSMCHARGE, 0)),nvl(property, '0'),
           SUM(trunc((duration + 599) / 600))
      from  rt_untelecomusage a ,service b, account c
     where a.starttime >
           to_date(to_char(add_months(to_date('200508 ', 'YYYYMM'), -1),
                           'YYYYMM') || '20235959',
                   'YYYYMMDDHH24MISS') and
           a.starttime < to_date('200508 ' || '21', 'YYYYMMdd') and
           gsmcharge > 0 and a.serviceid = b.serviceid and
           b.accountnum = c.accountnum
     group by b.SUBSIDIARYID,
              c.paymentmethod || ':' || nvl(subscribertype, '9999999'),
              'gsm',nvl(property, '0');
     該語句原先需要4,5個小時左右。

    優化:
    alter session set hash_area_size=300000000;

    select /*+ use_hash(b,c) ordered NO_EXPAND full(a) use_hash(a)*/  b.SUBSIDIARYID,c.paymentmethod || ':' || nvl(subscribertype, '9999999'),
         'gsm',count(*), sum(decode(untelLOCALCHARGE,0,decode(duration,0, 1,
            decode(sign(duration - 1800), 1,2 + trunc((duration - 1201) / 600), 2)),
         trunc((duration + 599) / 600))),sum(nvl(GSMCHARGE, 0)),
           nvl(property, '0'),SUM(trunc((duration + 599) / 600))
      from service b, account c,untelecomusage_200508  a
     where a.starttime >
           to_date(to_char(add_months(to_date('200508', 'YYYYMM'), -1),
                           'YYYYMM') || '20235959',
                   'YYYYMMDDHH24MISS') and
           a.starttime < to_date('200508' || '21', 'YYYYMMdd') and
           gsmcharge > 0 and a.serviceid = b.serviceid and
           b.accountnum = c.accountnum
     group by b.SUBSIDIARYID,c.paymentmethod || ':' || nvl(subscribertype, '9999999'),'gsm',nvl(property, '0'); 

     這樣優化后,只需要40分鐘左右即可。

    八.案例
    1. 循環Update操作
     
      以下過程太慢了, 半個小時連5000條記錄都未處理,總 共有7萬多條。
    declare
        cursor c1 is
        select caller
        from zxx_sms_step where chargemonth=200504 and fee is null;
        icnt number;
    begin
     icnt:=0;
     for m_c1 in c1 loop
      update zxx_sms_step a set fee=
       (select nvl(sum(pascharge),0) from ipasimport_200504 where caller=m_c1.caller and pastag in (1243,1251))
       where caller=m_c1.caller and chargemonth=200504;
      icnt:=icnt+1;
      if icnt=500 then
       exit;   
      end if;
     end loop;
    end;

       這樣的SQL語句,建議先將update中的子查詢生成一張中間表,然后再update。
    alter session set hash_area_size=400000000 ;

    select /*+use_hash(a,b)*/ b.caller,nvl(sum(a.pascharge),0) from ipasimport_200504 a,zxx_sms_step b
    where b.chargemonth=200504 and b.fee is null
    and a.caller=b.caller and a.pastag in (1243,1251)
    group by b.caller;
     這樣10分鐘不到就可產生中間表,然后再update只需幾分鐘即可。


    2. 部分表格未做統計信息分析
     
     網通OA系統自從oracle服務器從pc服務器上遷到小型機上后,其CPU利用率經常沖到很高。而其中每一個進程在某個瞬間將占用40%左右的CPU。這些進程都是通過jdbc thin client 連過來的。

    通過抓取其sql_text,發現以下兩條SQL語句不正常。
    1.
     SQL>  select D.flow_inid,D.step_inco,D.deal_man,D.agen_men,D.time_set,D.peri_man,
      2   S2.fsub_set,S2.fsub_id,F.mtbl_stru,F.doc_name,F.svr_name
      3   from deal_info D,step_inst S1,step_def S2,flow_inst F
      4   where D.step_inco=S1.step_inco and S1.flow_id=S2.flow_id
      5   and S1.step_code=S2.step_code and S1.flow_inid=F.flow_inid and D.step_type=5
      6   and D.fsub_flag is not null and D.fsub_flag=1 and rownum<=1;

    其執行計劃和統計信息如下:

    Execution Plan
    ----------------------------------------------------------
       0      SELECT STATEMENT Optimizer=CHOOSE (Cost=22 Card=1 Bytes=1077)
       1    0   COUNT (STOPKEY)
       2    1     NESTED LOOPS (Cost=22 Card=1 Bytes=1077)
       3    2       NESTED LOOPS (Cost=21 Card=1 Bytes=360)
       4    3         NESTED LOOPS (Cost=20 Card=1 Bytes=150)
       5    4           TABLE ACCESS (FULL) OF 'STEP_INST' (Cost=2 Card=9  Bytes=153)
       6    4           TABLE ACCESS (BY INDEX ROWID) OF 'DEAL_INFO' (Cost=2 Card=1 Bytes=133)
       7    6             INDEX (RANGE SCAN) OF 'DEAL_INFO_STEP_INCO' (NON-UNIQUE) (Cost=2
       8    3         TABLE ACCESS (BY INDEX ROWID) OF 'FLOW_INST' (Cost=1 Card=1 Bytes=210)
       9    8           INDEX (UNIQUE SCAN) OF 'PK_FLOW_INST' (UNIQUE)
      10    2       TABLE ACCESS (BY INDEX ROWID) OF 'STEP_DEF' (Cost=1 Card=1 Bytes=717)
      11   10         INDEX (UNIQUE SCAN) OF 'STEP_DEF_PK11119358638593' (UNIQUE)

    Statistics
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
         270626  consistent gets
            273  physical reads
              0  redo size
           1079  bytes sent via SQL*Net to client
            655  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              0  rows processed

    這條SQL語句執行的時間也不長,就幾秒鐘,但是我們看到consistent gets很高有27萬多,這個操作就是消耗CPU的禍首。從執行計劃來看,其執行計劃顯然不可理,問題出在表格的連接順序上面,應該是deal_info表格做為驅動表先訪問。

    檢查這些表格的統計分析,發現step_def表格未做分析,對該表格做統計信息分析,并對deal_info表做柱狀圖分析后:
    analyze table deal_info compute statistics for all indexed columns;

    其執行計劃正是我們所想要的,同時consistent gets也只有200左右,該操作所消耗的CPU也下降到了1%。

    2.表格的柱狀圖信息沒有分析:
    SELECT SO.SO_NBR, so_type.name,STATUS.STS_WORDS, SO.REMARKS, SO.CHECK_TYPE,CTRL_ASGN.DISPATCHED_DATE,
    CTRL_ASGN.PRE_ALARM_DATE, CTRL_ASGN.ALARM_DATE
    from SO,SO_HANDLE, CTRL_ASGN,so_type,status
    WHERE SO_HANDLE.SO_NBR=SO.SO_NBR AND SO.SO_NBR=CTRL_ASGN.SO_NBR
    AND SO_HANDLE.HANDLE_TYPE_ID=1017
    and so.so_type_id=so_type.so_type_id and so.PRIORITY=status.sts_id and status.table_name='SO'
     AND STATUS.column_name ='PRIORITY' AND SO_HANDLE.WORK_AREA_ID= 300101
    AND SO.STATE= 'B' AND SO.HALT ='N'
    AND CTRL_ASGN.STATE = 'B'
    AND CTRL_ASGN.STS = 'D';

    該SQL語句執行時間要2分鐘左右。
    執行計劃如下:
    Execution Plan
    ----------------------------------------------------------
       0      SELECT STATEMENT Optimizer=HINT: RULE
       1    0   NESTED LOOPS
       2    1     NESTED LOOPS
       3    2       NESTED LOOPS
       4    3         NESTED LOOPS
       5    4           TABLE ACCESS (BY INDEX ROWID) OF 'STATUS'
       6    5             INDEX (RANGE SCAN) OF 'PK_STATUS' (UNIQUE)
       7    4           TABLE ACCESS (BY INDEX ROWID) OF 'CTRL_ASGN'
       8    7             INDEX (RANGE SCAN) OF 'CTRL_ASGN_0002'
       9    3         TABLE ACCESS (BY INDEX ROWID) OF 'SO'
      10    9           INDEX (UNIQUE SCAN) OF 'PK_SO' (UNIQUE)
      11    2       TABLE ACCESS (BY INDEX ROWID) OF 'SO_TYPE'
      12   11         INDEX (UNIQUE SCAN) OF 'PK_SO_TYPE' (UNIQUE)
      13    1     TABLE ACCESS (BY INDEX ROWID) OF 'SO_HANDLE'
      14   13       INDEX (RANGE SCAN) OF 'PK_SO_HANDLE' (UNIQUE)

    我們收集表格信息和結果集的信息:
    SQL> select count(*) from CTRL_ASGN;
      COUNT(*)
    ----------
       1832469
    SQL> select count(*) from status;
      COUNT(*)
    ----------
          1718

    SQL> select count(*) from so;
      COUNT(*)
    ----------
        300296

    SQL> select count(*) from so_type;
      COUNT(*)
    ----------
           265

    SQL> select count(*) from so_handle;
      COUNT(*)
    ----------
       1296263  

    select count(*) from ctrl_asgn where  CTRL_ASGN.STATE = 'B' AND CTRL_ASGN.STS = 'D';
      COUNT(*)
    ----------
        331490
          
    select count(*) from so where SO.STATE= 'B' AND SO.HALT ='N';
      COUNT(*)
    ----------
           361
          
    select count(*) from so_handle where SO_HANDLE.HANDLE_TYPE_ID=1017 and SO_HANDLE.WORK_AREA_ID= 300101;
      COUNT(*)
    ----------
         30086

    通過對上面這些信息進行分析,我們可以發現這個問題也可以歸結為表格之間的連接順序上面。通過將SO表做柱狀圖分析后,該SQL語句只需1秒鐘即可出來。
    Analyze table so compute statistics for all indexed columns;

    執行計劃變成如下:
    Execution Plan
    ----------------------------------------------------------
       0      SELECT STATEMENT Optimizer=CHOOSE (Cost=273 Card=32 Bytes=3936)
       1    0   NESTED LOOPS (Cost=273 Card=32 Bytes=3936)
       2    1     NESTED LOOPS (Cost=153 Card=30 Bytes=2730)
       3    2       HASH JOIN (Cost=33 Card=30 Bytes=2130)
       4    3         NESTED LOOPS (Cost=31 Card=30 Bytes=1620)
       5    4           TABLE ACCESS (FULL) OF 'STATUS' (Cost=2 Card=1 Bytes=25)
       6    4           TABLE ACCESS (BY INDEX ROWID) OF 'SO' (Cost=29 Card=59 Bytes=1711)
       7    6             INDEX (RANGE SCAN) OF 'SO_0003' (NON-UNIQUE) (Cost=2 Card=59)
       8    3         TABLE ACCESS (FULL) OF 'SO_TYPE' (Cost=1 Card=128 Bytes=2176)
       9    2       TABLE ACCESS (BY INDEX ROWID) OF 'SO_HANDLE' (Cost=4 Card=280 Bytes=5600)
      10    9         INDEX (RANGE SCAN) OF 'PK_SO_HANDLE' (UNIQUE) (Cost=3 Card=280)
      11    1     TABLE ACCESS (BY INDEX ROWID) OF 'CTRL_ASGN' (Cost=4 Card=13620 Bytes=435840)
      12   11       INDEX (RANGE SCAN) OF 'CTRL_ASGN_0003' (NON-UNIQUE) (Cost=2 Card=13620)

     

    3. Not exists的使用
    --停機保號用戶數(除欠費)
    select 'XJ'||1||'180','停機保號用戶數',count(distinct serviceid),1,'200509',groupid from cbq_lch_usage0
    where subsidiaryid=1 and subid<>'02'  and subid<>'06' and status='7' and
    serviceid not in (select serviceorderid from cbq_qf_usage1  where status<>'3' and status <> '8')
    group by 'XJ'||1||'180','停機保號用戶數',1,'200509',groupid ;

    Execution Plan
    ----------------------------------------------------------
       0      SELECT STATEMENT Optimizer=RULE
       1    0   SORT (GROUP BY)
       2    1     FILTER
       3    2       TABLE ACCESS (FULL) OF 'CBQ_LCH_USAGE0'
       4    2       TABLE ACCESS (FULL) OF 'CBQ_QF_USAGE1'

    Elapsed: 13:48:26.85

    調整:
    not in 改成not exists
    create index idx_serviceorderid on cbq_qf_usage1(serviceorderid) nologging;

    select 'XJ'||1||'180','停機保號用戶數',count(distinct serviceid),1,'200509',a.groupid
    from cbq_lch_usage0 a
    where a.subsidiaryid=1 and a.subid<>'02'  and a.subid<>'06' and a.status='7'
    and not exists(select 1 from cbq_qf_usage1 b where status<>'3' and status<>'8' and a.serviceid=b.serviceorderid)
    group by 'XJ'||1||'180','停機保號用戶數',1,'200509',a.groupid;

    Execution Plan
    ----------------------------------------------------------
       0      SELECT STATEMENT Optimizer=RULE
       1    0   SORT (GROUP BY)
       2    1     FILTER
       3    2       TABLE ACCESS (FULL) OF 'CBQ_LCH_USAGE0'
       4    2       TABLE ACCESS (BY INDEX) OF 'CBQ_QF_USAGE1'
       5    4         INDEX (RANGE SCAN) OF 'IDX_SERVICEORDERID'

    Elapsed: 00:00:01.36


    九.其他
    1.SELECT子句中避免使用 ‘ * ‘
    當你想在SELECT子句中列出所有的COLUMN時,使用動態 SQL列引用 ‘*’ 是一個方便的方法.不幸的是,這是一個非常低效的方法. 實際上,ORACLE在解析的過程中, 會將’*’ 依次轉換成所有的列名, 這個工作是通過查詢數據字典完成的, 這意味著將耗費更多的時間.
    2.用TRUNCATE替代DELETE
    3.使用表的別名(Alias)
    當在SQL語句中連接多個表時, 請使用表的別名并把別名前綴于每個Column上.這樣一來,就可以減少解析的時間并減少那些由Column歧義引起的語法錯誤.


    4.索引的等級
     一般情況索引等級如下:
     a) 等式比較比范圍比較要高。
     b) 唯一性索引比非唯一性索引要高。
     c) 一般情況下單列索引等級要比復合索引高,但如果where子句中包含所  有復合索引的字段,則復合索引等級高。
     例如:
    SELECT col1, ...
    FROM emp
    WHERE emp_name = 'GURRY'
    AND emp_no = 127
    AND dept_no = 12

    Index1 (emp_name)
    Index2 (emp_no, dept_no, emp_name)
    ORACLE將使用索引Index2。

    5.統計信息分析
    在現實當中,有關analyze分析有以下兩種誤區:

    a) 只要對主要的或者關鍵的表格做分析即可。其實正確的應該是需要對所有涉及到的表格都做過分析。

    b) 做一次分析后即可高枕無憂。事實上,一旦做過分析后,就應該定期更新這些統計信息,以保證統計信息的正確性。

    6.Exists總比In快
     有許多人認為用Exists總比用In要快,這也是一個誤區。有時用in反而比用Exists快。
    他們之間的區別如下:
      IN subquery,首先執行subquery,由subquery來驅動父查詢。而Exists子查詢則由父查詢來驅動子查詢。這就是兩者之間的區別。
     所以如果子查詢小的話,則可以采用in會快一些,如果子查詢大的話,則采用exists會快一些。

    7.>與>=
     大于或小于操作符一般情況下是不用調整的,因為它有索引就會采用索引查找,但有的情況下可以對它進行優化,如一個表有100萬記錄,一個數值型字段A,
    30萬記錄的A=0,30萬記錄的A=1,39萬記錄的A=2,1萬記錄的A=3。
     那么執行A>2與A>=3的效果就有很大的區別了,因為A>2時ORACLE會先找出
    為2的記錄索引再進行比較,而A>=3時ORACLE則直接找到=3的記錄索引。

    8. 使用索引來避免排序
      索引是排好序的,在某些情況下可以使用索引來避免排序。
      SELECT acc_name, acc_surname
      FROM account acct
      ORDER BY 1;

      SELECT /*+ INDEX_ASC(acct acc_ndx1) */ acc_name,acc_surname
      FROM account acct;


    9.大對象操作
     
    a)Big Insert
    (1)direct insert(serial and parallel)
    insert /*+append*/into tab1 select * from tab2;
           Insert /*+append parallel(emp,8)*/ into emp  select * from emp_bak;
    (2)nologging
             insert into tab1 nologging select * from tab2;
        (3)Large extent size
         更大的extent可以獲得更好的insert性能。
     (5)Large rollback segment

    b)Large Index Create
      大的索引extent size值
        大的Sort_area_size值
      采用nologging
      采用parallel
      大的臨時表空間

    alter session sort_area_size=100000000;
    create index xxx on aa(ab) nologging parallel 2;

     c)Large Delete
    分幾次delete。

     

     


    附錄一
    Hint全集
    174. /*+ALL_ROWS*/

      表明對語句塊選擇基于開銷的優化方法,并獲得最佳吞吐量,使資源消耗最小化.例如:
    SELECT /*+ALL+_ROWS*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='CCBZZP';

      175. /*+FIRST_ROWS*/

      表明對語句塊選擇基于開銷的優化方法,并獲得最佳響應時間,使資源消耗最小化.例如:
    SELECT /*+FIRST_ROWS*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='CCBZZP';

      176. /*+CHOOSE*/

      表明如果數據字典中有訪問表的統計信息,將基于開銷的優化方法,并獲得最佳的吞吐量;表明如果數據字典中沒有訪問表的統計信息,將基于規則開銷的優化方法;例如:
    SELECT /*+CHOOSE*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='CCBZZP';

      177. /*+ RULE*/

      表明對語句塊選擇基于規則的優化方法.例如:
    SELECT /*+ RULE */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='CCBZZP'; 

      178. /*+ FULL(TABLE)*/

      表明對表選擇全局掃描的方法.例如:
    SELECT /*+FULL(A)*/ EMP_NO,EMP_NAM FROM BSEMPMS A WHERE EMP_NO='CCBZZP';

      179. /*+ROWID(TABLE)*/

      提示明確表明對指定表根據ROWID進行訪問.例如:
    SELECT /*+ROWID(BSEMPMS)*/ * FROM BSEMPMS WHERE ROWID>='AAAAAAAAAAAAAA'
    AND EMP_NO='CCBZZP';

      180. /*+CLUSTER(TABLE)*/
     
      提示明確表明對指定表選擇簇掃描的訪問方法,它只對簇對象有效.例如:
    SELECT /*+CLUSTER */ BSEMPMS.EMP_NO,DPT_NO FROM BSEMPMS,BSDPTMS
    WHERE DPT_NO='TEC304' AND BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

    181. /*+ INDEX(TABLE   INDEX_NAME)*/
    /*+index(table ind_name) index(table ind_name)*/
    表明對表選擇索引的掃描方法.例如:
    SELECT /*+INDEX(BSEMPMS SEX_INDEX) USE SEX_INDEX BECAUSE THERE ARE FEWMALE BSEMPMS */ FROM BSEMPMS WHERE SEX='M';

      182. /*+INDEX_ASC(TABLE INDEX_NAME)*/

      表明對表選擇索引升序的掃描方法.例如:
    SELECT /*+INDEX_ASC(BSEMPMS PK_BSEMPMS) */ FROM BSEMPMS WHERE DPT_NO='CCBZZP';

      183. /*+INDEX_COMBINE*/

      為指定表選擇位圖訪問路經,如果INDEX_COMBINE中沒有提供作為參數的索引,將選擇出位圖索引的布爾組合方式.例如:
    SELECT /*+INDEX_COMBINE(BSEMPMS SAL_BMI HIREDATE_BMI)*/ * FROM BSEMPMS
    WHERE SAL<5000000 AND HIREDATE

      184. /*+INDEX_JOIN(TABLE INDEX_NAME)*/

      提示明確命令優化器使用索引作為訪問路徑.例如:
    SELECT /*+INDEX_JOIN(BSEMPMS SAL_HMI HIREDATE_BMI)*/ SAL,HIREDATE
    FROM BSEMPMS WHERE SAL<60000;

      185. /*+INDEX_DESC(TABLE INDEX_NAME)*/

      表明對表選擇索引降序的掃描方法.例如:
    SELECT /*+INDEX_DESC(BSEMPMS PK_BSEMPMS) */ FROM BSEMPMS WHERE DPT_NO='CCBZZP';

      186. /*+INDEX_FFS(TABLE INDEX_NAME)*/

      對指定的表執行快速全索引掃描,而不是全表掃描的辦法.例如:
    SELECT /*+INDEX_FFS(BSEMPMS IN_EMPNAM)*/ * FROM BSEMPMS WHERE DPT_NO='TEC305';

      187. /*+ADD_EQUAL TABLE INDEX_NAM1,INDEX_NAM2,...*/

      提示明確進行執行規劃的選擇,將幾個單列索引的掃描合起來.例如:
    SELECT /*+INDEX_FFS(BSEMPMS IN_DPTNO,IN_EMPNO,IN_SEX)*/ * FROM BSEMPMS WHERE EMP_NO='CCBZZP' AND DPT_NO='TDC306';

      188. /*+USE_CONCAT*/

      對查詢中的WHERE后面的OR條件進行轉換為UNION ALL的組合查詢.例如:
    SELECT /*+USE_CONCAT*/ * FROM BSEMPMS WHERE DPT_NO='TDC506' AND SEX='M';

      189. /*+NO_EXPAND*/

      對于WHERE后面的OR 或者IN-LIST的查詢語句,NO_EXPAND將阻止其基于優化器對其進行擴展.例如:
    SELECT /*+NO_EXPAND*/ * FROM BSEMPMS WHERE DPT_NO='TDC506' AND SEX='M';

      190. /*+NOWRITE*/

      禁止對查詢塊的查詢重寫操作.

    191. /*+REWRITE*/

      可以將視圖作為參數.

      192. /*+MERGE(TABLE)*/

      能夠對視圖的各個查詢進行相應的合并.例如:
    SELECT /*+MERGE(V) */ A.EMP_NO,A.EMP_NAM,B.DPT_NO FROM BSEMPMS A (SELET DPT_NO
    ,AVG(SAL) AS AVG_SAL FROM BSEMPMS B GROUP BY DPT_NO) Va WHERE A.DPT_NO=V.DPT_NO
    AND A.SAL>V.AVG_SAL;

      193. /*+NO_MERGE(TABLE)*/

      對于有可合并的視圖不再合并.例如:
    SELECT /*+NO_MERGE(V) */ A.EMP_NO,A.EMP_NAM,B.DPT_NO FROM BSEMPMS A (SELET DPT_NO
    ,AVG(SAL) AS AVG_SAL FROM BSEMPMS B GROUP BY DPT_NO) V WHERE A.DPT_NO=V.DPT_NO
    AND A.SAL>V.AVG_SAL;

      194. /*+ORDERED*/

      根據表出現在FROM中的順序,ORDERED使ORACLE依此順序對其連接.例如:
    SELECT /*+ORDERED*/ A.COL1,B.COL2,C.COL3 FROM TABLE1 A,TABLE2 B,TABLE3 C
    WHERE A.COL1=B.COL1 AND B.COL1=C.COL1;

      195. /*+USE_NL(TABLE)*/

      將指定表與嵌套的連接的行源進行連接,并把指定表作為內部表.例如:
    SELECT /*+ORDERED USE_NL(BSEMPMS)*/ BSDPTMS.DPT_NO,BSEMPMS.EMP_NO,BSEMPMS.EMP_NAM FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

      196. /*+USE_MERGE(TABLE)*/

      將指定的表與其他行源通過合并排序連接方式連接起來.例如:
    SELECT /*+USE_MERGE(BSEMPMS,BSDPTMS)*/ * FROM BSEMPMS,BSDPTMS WHERE
    BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

      197. /*+USE_HASH(TABLE)*/

      將指定的表與其他行源通過哈希連接方式連接起來.例如:
    SELECT /*+USE_HASH(BSEMPMS,BSDPTMS)*/ * FROM BSEMPMS,BSDPTMS WHERE
    BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

      198. /*+DRIVING_SITE(TABLE)*/

      強制與ORACLE所選擇的位置不同的表進行查詢執行.例如:
    SELECT /*+DRIVING_SITE(DEPT)*/ * FROM BSEMPMS,DEPT@BSDPTMS WHERE BSEMPMS.DPT_NO=DEPT.DPT_NO;

      199. /*+LEADING(TABLE)*/

      將指定的表作為連接次序中的首表.

    200. /*+CACHE(TABLE)*/

      當進行全表掃描時,CACHE提示能夠將表的檢索塊放置在緩沖區緩存中最近最少列表LRU的最近使用端例如:
    SELECT /*+FULL(BSEMPMS) CAHE(BSEMPMS) */ EMP_NAM FROM BSEMPMS;

      201. /*+NOCACHE(TABLE)*/

      當進行全表掃描時,CACHE提示能夠將表的檢索塊放置在緩沖區緩存中最近最少列表LRU的最近使用端,例如:
    SELECT /*+FULL(BSEMPMS) NOCAHE(BSEMPMS) */ EMP_NAM FROM BSEMPMS;

      202. /*+APPEND*/

      直接插入到表的最后,可以提高速度.
    insert /*+append*/ into test1 select * from test4 ;

      203. /*+NOAPPEND*/

      通過在插入語句生存期內停止并行模式來啟動常規插入.
    insert /*+noappend*/ into test1 select * from test4;

    附錄二
    STATSPACK包的使用指南
    1.oracle8.1.6開始引進statspack,statspack是診斷oracle性能的強有力的工具。
    2.安裝前準備
     A.首先是系統參數的確認:
    job_query_processes:為了建立自動任務,執行數據收集,該參數要大于0
    time_statistics:為了收集操作系統計時信息等,需要將其設置為TRUE

    B.建議最好是單獨的為perfstat用戶(即安裝statspack要建的用戶)單獨建立數據表空間和臨時表空間,數據表空間至少要有100M的空閑空間,否則創建statspack對象會失敗,如果打算長期使用statspack,可以考慮建稍大些的數據表空間。
    3.安裝
    A.安裝腳本
    安裝的腳本所在目錄是$ORACLE_HOME/rdbms/admin,在oracle8.1.6版本安裝腳本是statscre.sql,之后 8.1.7版本開始就是spcreate.sql,安裝所需用戶在9i之前的需要internal或者擁有sysdba權限的用戶,9i需要的用戶是 sys(9i已經不存在internal用戶了)
    執行安裝腳本如下:
    SQL> @$ORACLE_HOME/rdbms/admin/spcreate
     
    B. 在安裝過程中,需要填寫perfstat用戶的密碼,并且選擇perfstat用戶的數據表空間和臨時表空間,安裝完成之后,察看相應的.lis文件檢查安裝是否正確無誤,有問題可以通過spdrop.sql完成statspack的卸載,重新運行spcreate.sql完成statspack的安裝。

    4.  測試
    最簡單的statspack報告生成,運行兩次statspack.snap,然后運行spreport.sql生成一個基于兩個時間點的報告。如果是8.1.7.3之前版本的Oracle,需要修改spcpkg.sql,要將substr修改為substrb,如下位置:
           select l_snap_id
                , p_dbid
                , p_instance_number
                , substr(sql_text,1,31) ? substrb(sql_text,1,31)
     
    建立簡單的statspack報告過程如下:
    SQL> execute statspack.snap (i_snap_level=>10)
    PL/SQL procedure successfully completed.
    SQL> execute statspack.snap
    PL/SQL procedure successfully completed.
    SQL> @$ORACLE_HOME/rdbms/admin/spreport
     
    Spreport的執行過程中會列出需要選擇的快照,你需要填寫該報告描述的開始和結束的快照序號,并填寫報告的文件名,當然可以不填,使用默認的報告文件名,默認的會生成在目錄$ORACLE_HOME/rdbms/admin中
    這樣就可以驗證statspack已經正確的安裝完成了
     
    自動收集statspack快照
    正常在真正的環境下,我們是需要連續的采樣一段時間,這樣生成的statspack才能更好的反映系統的現狀,我們是可以通過spauto.sql來自動收集數據的。
     
    主要可能會設計到修改如下部分的內容
    variable jobno number;
    variable instno number;
    begin
      select instance_number into :instno from v$instance;
      dbms_job.submit(:jobno, 'statspack.snap;', trunc(sysdate+1/24,'HH'), 'trunc(SYSDATE+1/24,''HH'')', TRUE, :instno);
      commit;
    end;
    /
    主要是修改1/24這個值,目前是一個小時自動收集一次數據,如果要改動為半個小時收集一次數據就修改為1/48,同理,進行或大或小的修改。
     
    執行后,可以在spauto.lis文件中看到當前自動收集數據的job號等信息。當想要生成statspack報告的時候,只要選擇任何兩個不跨越停機時間的快照序號就可以了。注意,statspack是不能跨越停機的。

    posted on 2009-07-25 16:32 小菜毛毛 閱讀(517) 評論(0)  編輯  收藏 所屬分類: 數據庫
    主站蜘蛛池模板: 亚洲gv白嫩小受在线观看| 亚洲欧美成aⅴ人在线观看| AV无码免费永久在线观看| 亚洲日韩AV无码一区二区三区人| 免费va在线观看| 久久久精品2019免费观看 | 亚洲中文字幕无码爆乳app| 亚洲国产精品国产自在在线| 久久免费福利视频| 亚洲A∨精品一区二区三区下载 | 亚洲人成在线电影| 国产精品免费看久久久无码| 免费91最新地址永久入口| 久久亚洲精品国产精品婷婷| 亚洲一区无码中文字幕| 成年人视频在线观看免费| 日韩精品无码免费专区网站 | 日韩大片免费观看视频播放| 亚洲美女视频网站| 精品国产亚洲一区二区在线观看 | 免费h成人黄漫画嘿咻破解版| 99爱在线精品视频免费观看9| 三年片在线观看免费观看大全中国 | 亚洲日韩精品无码专区网站| 日本免费网址大全在线观看| 中国内地毛片免费高清| 亚洲av中文无码字幕色不卡| 亚洲精品国产手机| 亚洲精品无码久久千人斩| 国产大片免费观看中文字幕| 免费H网站在线观看的| 国产免费阿v精品视频网址| 看成年女人免费午夜视频| 亚洲另类视频在线观看| 亚洲AV无码乱码在线观看裸奔 | 久久精品国产亚洲Aⅴ香蕉| 免费看AV毛片一区二区三区| 久久www免费人成看片| 无码少妇精品一区二区免费动态 | 中文字幕av无码无卡免费| 无码精品国产一区二区三区免费 |