ORACLE 9i資料庫優化案例(2) --- 單列索引變複合索引的優化

junsansi發表於2010-11-30

方案實施過程及分析來啦:

越看越激動是吧,修改前後,從數倍,數十倍直到數百倍的效能差距,這究竟是怎麼實現的呢,要回答這個問題,我們還是要從源頭說起~~

我個人非常認同這樣的觀點:大多數的效能優化方案,最終都是要落實到SQL優化的層面來!!這裡,我們進行的調優操作,也是要從優化SQL語句著手!

我曾經提到過一種觀點:所謂調優,就是儘可能少讀並且儘可能少寫!!能否少寫很多時候DBA不能直接掌控,但讓它少讀,就本次案例來說,還是有辦法的。

從TOP等待事件來看,主要等待也是磁碟檔案讀,如果我們能夠讓它少讀一點(嚴重強調,"一點"也很重要,一條SQL語句每次如果能少產生幾百次IO,那該語句執行個幾萬次後,算下來也相當於節省了近千萬次IO呢),對於系統整體負載就能起到重要的作用。

A>. 單列索引變複合索引

從之前的ORACLE報表中可以看到,佔用資源最多的其實就那麼四五條SQL語句,執行了很多次,佔用了大量的CPU資源,產生了巨量的邏輯讀/物理讀,其中最顯著的是這兩條:

                                                         CPU      Elapsd

      Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value

    --------------- ------------ -------------- ------ -------- --------- ----------

        450,247,504        1,974      228,088.9   32.1  2445.17   2405.51 1551938908

    select count(1) as num from t_wiki_doc_topics where doc_id_encry

    pt='BBwReRlFZV0RRXlkG' and topic_state=1

        446,599,884        1,959      227,973.4   31.8  2450.06   2406.25 1250588569

    select b.* from (select rownum as r,a.* from (select t.user_id,t

    .last_post_user_id,t.doc_title,t.topic_title,t.click_count+1 as

    click_count,t.posts_count-1 as posts_count,t.user_nick,to_char(t

    .last_post_time,'yy-mm-dd hh24:mi:ss') last_post_time,t.last_pos

    t_user_nick,t.last_post_user_id_encrypt,t.user_id_encrypt,t.onto

    提示:

    第二條語句由於report指令碼的限制,沒有完全顯示,可以通過查詢v$sql獲取完整SQL語句,以協助分析。

這兩語句佔用了一半以上的系統資源,訪問的物件相同都是t_wiki_doc_topics表,該表的查詢列之一doc_id_encrypt上建有索引,單條語句執行效率亦可接受,基本都是在s即可得到結果,但,由於查詢涉及資料量和查詢次數,仍然產生了大量的邏輯IO和CPU資源的佔用。

首先檢視第1條語句的執行計劃:

    SQL> explain plan for

      2  select count(1) as num from t_wiki_doc_topics where doc_id_encrypt='BBwReRlFZV0RRXlkG' and topic_state=1;

    Explained

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

    PLAN_TABLE_OUTPUT

    --------------------------------------------------------------------------------

    --------------------------------------------------------------------------------

    | Id  | Operation                    |  Name                         | Rows  | B

    --------------------------------------------------------------------------------

    |   0 | SELECT STATEMENT             |                               |     1 |

    |   1 |  SORT AGGREGATE              |                               |     1 |

    |*  2 |   TABLE ACCESS BY INDEX ROWID| T_WIKI_DOC_TOPICS             |     1 |

    |*  3 |    INDEX RANGE SCAN          | IDX_DOC_TOPIC_DOC_ID_ENCRYPT  |     1 |

    --------------------------------------------ex------------------------------------

    Predicate Information (identified by operation id):

    ---------------------------------------------------

       2 - filter("T_WIKI_DOC_TOPICS"."TOPIC_STATE"=1)

       3 - access("T_WIKI_DOC_TOPICS"."DOC_ID_ENCRYPT"='BBwReRlFZV0RRXlkG')

    Note: cpu costing is off

    17 rows selected

從上述計劃可以看出,雖然有索引,由於還需要返回表中讀取資料,以過濾topic_state,因此一旦執行次數頻繁,就會迭代產生更多IO。接下來我們再看看第2條語句:

    SQL> explain plan for

      2  

      2   select b.*

      3     from (select rownum as r, a.*

      4             from (select t.user_id,

      5                          t.last_post_user_id,

      6                          t.doc_title,

      7                          t.topic_title,

      8                          t.click_count + 1 as click_count,

      9                          t.posts_count - 1 as posts_count,

     10                          t.user_nick,

     11                          to_char(t.last_post_time, 'yy-mm-dd hh24:mi:ss') last_post_time,

     12                          t.last_post_user_nick,

     13                          t.last_post_user_id_encrypt,

     14                          t.user_id_encrypt,

     15                          t.ontop_sort,

     16                          t.is_valuable,

     17                          t.doc_topics_id_encrypt,

     18                          t.is_ontop,

     19                          t.user_ip,

     20                          t.last_post_user_ip,

     21                          t.topic_type

     22                     from t_wiki_doc_topics t

     23                    where t.doc_id_encrypt = 'BBwReRlFZV0RRXlkG'

     24                      and t.topic_state = 1

     25                    order by t.is_ontop       desc,

     26                             t.ontop_sort     desc,

     27                             t.last_post_time desc) a

     28            where rownum <= 40) b

     29   where b.r >= 1

     30  /

    Explained

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

    PLAN_TABLE_OUTPUT

    --------------------------------------------------------------------------------

    --------------------------------------------------------------------------------

    | Id  | Operation                       |  Name                         | Rows

    --------------------------------------------------------------------------------

    |   0 | SELECT STATEMENT                |                               |     1

    |*  1 |  VIEW                           |                               |     1

    |*  2 |   COUNT STOPKEY                 |                               |

    |   3 |    VIEW                         |                               |     1

    |*  4 |     SORT ORDER BY STOPKEY       |                               |     1

    |*  5 |      TABLE ACCESS BY INDEX ROWID| T_WIKI_DOC_TOPICS             |     1

    |*  6 |       INDEX RANGE SCAN          | IDX_DOC_TOPIC_DOC_ID_ENCRYPT  |     1

    --------------------------------------------------------------------------------

    Predicate Information (identified by operation id):

    ---------------------------------------------------

       1 - filter("B"."R">=1)

       2 - filter(ROWNUM<=40)

       4 - filter(ROWNUM<=40)

       5 - filter("T"."TOPIC_STATE"=1)

    PLAN_TABLE_OUTPUT

    --------------------------------------------------------------------------------

       6 - access("T"."DOC_ID_ENCRYPT"='BBwReRlFZV0RRXlkG')

    Note: cpu costing is off

    23 rows selected

經過分析可以看到基本過濾條件與第一條相同:from t_wiki_doc_topics t where t.doc_id_encrypt = 'BBwReRlFZV0RRXlkG' and t.topic_state = 1。

對於這種型別的語句,我個人認為最簡單的辦法,就是刪除原doc_id_encrypt的舊索引,並建立新的複合索引(doc_id_encrypt+topic_state)即可。

執行建立指令碼如下:

    SQL> drop index IDX_DOC_TOPIC_DOC_ID_ENCRYPT;

    Index dropped

    SQL> create index ind_t_wiki_doc_topics_id_stat on t_wiki_doc_topics (doc_id_encrypt,topic_state);

    Index created

建立完新索引之後,重新檢視執行計劃:

    SQL> explain plan for

      2  select count(1) as num from t_wiki_doc_topics where doc_id_encrypt='BBwReRlFZV0RRXlkG' and topic_state=1;

    Explained

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

    PLAN_TABLE_OUTPUT

    --------------------------------------------------------------------------------

    --------------------------------------------------------------------------------

    | Id  | Operation            |  Name                          | Rows  | Bytes |

    --------------------------------------------------------------------------------

    |   0 | SELECT STATEMENT     |                                |     1 |    19 |

    |   1 |  SORT AGGREGATE      |                                |     1 |    19 |

    |*  2 |   INDEX RANGE SCAN   | IND_T_WIKI_DOC_TOPICS_ID_STAT  |     1 |    19 |

    --------------------------------------------------------------------------------

    Predicate Information (identified by operation id):

    ---------------------------------------------------

       2 - access("T_WIKI_DOC_TOPICS"."DOC_ID_ENCRYPT"='BBwReRlFZV0RRXlkG' AND

                  "T_WIKI_DOC_TOPICS"."TOPIC_STATE"=1)

    Note: cpu costing is off

    16 rows selected

    SQL> explain plan for

      2  

      2   select b.*

      3     from (select rownum as r, a.*

      4             from (select t.user_id,

      5                          t.last_post_user_id,

      6                          t.doc_title,

      7                          t.topic_title,

      8                          t.click_count + 1 as click_count,

      9                          t.posts_count - 1 as posts_count,

     10                          t.user_nick,

     11                          to_char(t.last_post_time, 'yy-mm-dd hh24:mi:ss') last_post_time,

     12                          t.last_post_user_nick,

     13                          t.last_post_user_id_encrypt,

     14                          t.user_id_encrypt,

     15                          t.ontop_sort,

     16                          t.is_valuable,

     17                          t.doc_topics_id_encrypt,

     18                          t.is_ontop,

     19                          t.user_ip,

     20                          t.last_post_user_ip,

     21                          t.topic_type

     22                     from t_wiki_doc_topics t

     23                    where t.doc_id_encrypt = 'BBwReRlFZV0RRXlkG'

     24                      and t.topic_state = 1

     25                    order by t.is_ontop       desc,

     26                             t.ontop_sort     desc,

     27                             t.last_post_time desc) a

     28            where rownum <= 40) b

     29   where b.r >= 1

     30  ;

    Explained

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

    PLAN_TABLE_OUTPUT

    --------------------------------------------------------------------------------

    --------------------------------------------------------------------------------

    | Id  | Operation                       |  Name                          | Rows

    --------------------------------------------------------------------------------

    |   0 | SELECT STATEMENT                |                                |     1

    |*  1 |  VIEW                           |                                |     1

    |*  2 |   COUNT STOPKEY                 |                                |

    |   3 |    VIEW                         |                                |     1

    |*  4 |     SORT ORDER BY STOPKEY       |                                |     1

    |   5 |      TABLE ACCESS BY INDEX ROWID| T_WIKI_DOC_TOPICS              |     1

    |*  6 |       INDEX RANGE SCAN          | IND_T_WIKI_DOC_TOPICS_ID_STAT  |     1

    --------------------------------------------------------------------------------

    Predicate Information (identified by operation id):

    ---------------------------------------------------

       1 - filter("B"."R">=1)

       2 - filter(ROWNUM<=40)

       4 - filter(ROWNUM<=40)

       6 - access("T"."DOC_ID_ENCRYPT"='BBwReRlFZV0RRXlkG' AND "T"."TOPIC_STATE"=1)

    PLAN_TABLE_OUTPUT

    --------------------------------------------------------------------------------

    Note: cpu costing is off

    22 rows selected

對於第1條語句只需要通過索引,即可以得到資料,避免了TABLE ACCESS BY INDEX ROWID的操作;對於第2條語句,雖然索引不能提供所有要訪問的列,TABLE ACCESS BY INDEX ROWID不可避免,但是我們要想到,索引本身也是過濾,應用複合索引後得到的資料就是符合條件的記錄,這時只需要返回基表獲得其它列的資訊即可,而無須再做資料的filter,再加上COUNT STOPKEY的作用,仍然可以實現只需要很少的讀即可實現需求。

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

相關文章