程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> Oracle數據庫 >> 關於Oracle數據庫 >> 解析一個通過添加本地分區索引提高SQL性能的案例

解析一個通過添加本地分區索引提高SQL性能的案例

編輯:關於Oracle數據庫
    今天接到同事求助,說有一個select query,在Oracle上要跑一分多鐘,他希望能在5s內出結果,以下就是解決這個問題的方法,需要的朋友可以參考下  

    該sql如下:

    復制代碼 代碼如下:
    Select  /*+ parallel(src, 8) */ distinct
      src.systemname as systemname
      ,  src.databasename as databasename
      ,  src.tablename as tablename
      ,  src.username as username
    from  <STRONG>meta_dbql_table_usage_exp_hst</STRONG> src
     inner <STRONG>join DR_QRY_LOG_EXP_HST</STRONG> rl on
      <STRONG>src.acctstringdate = rl.acctstringdate
      and src.queryid = rl.queryid</STRONG>
      And Src.Systemname = Rl.Systemname
      and src.acctstringdate > sysdate - 30
      And Rl.Acctstringdate > Sysdate - 30
     inner join  <STRONG>meta_dr_qry_log_tgt_all_hst </STRONG>tgt on
      upper(tgt.systemname) = upper('MOZART')
      And Upper(tgt.Databasename) = Upper('GDW_TABLES')
      And Upper(tgt.Tablename) = Upper('SSA_SLNG_LSTG_MTRC_SD')
      <STRONG>AND src.acctstringdate = tgt.acctstringdate
      and rl.statement_id = tgt.statement_id</STRONG>
      and rl.systemname = tgt.systemname
      And Tgt.Acctstringdate > Sysdate - 30
      And Not(
        Upper(Tgt.Systemname)=Upper(src.systemname)
        And
        Upper(Tgt.Databasename) = Upper(Src.Databasename)
        And
        Upper(Tgt.Tablename) = Upper(Src.Tablename)
        )
      And   tgt.Systemname is not null
      And   tgt.Databasename Is Not Null
      And   tgt.tablename is not null
     


    SQL的簡單分析
    總 得來看,這個SQL就是三個表 (meta_dbql_table_usage_exp_hst,DR_QRY_LOG_EXP_HST,meta_dr_qry_log_tgt_all_hst) 的INNER JOIN,這三個表數據量都在百萬級別,且都是分區表(以acctstringdate為分區鍵),執行計劃如下:

    復制代碼 代碼如下:
    ------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                              | Name                          | Rows  | Bytes | Cost  | Pstart| Pstop |
    ------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                       |                               |     1 |   159 |  8654 |       |       |
    |   1 |  PX COORDINATOR                        |                               |       |       |       |       |       |
    |   2 |   PX SEND QC (RANDOM)                  | :TQ10002                      |     1 |   159 |  8654 |       |       |
    |   3 |    SORT UNIQUE                         |                               |     1 |   159 |  8654 |       |       |
    |   4 |     PX RECEIVE                         |                               |     1 |    36 |     3 |       |       |
    |   5 |      PX SEND HASH                      | :TQ10001                      |     1 |    36 |     3 |       |       |
    |*  6 |       TABLE ACCESS BY LOCAL INDEX ROWID| DR_QRY_LOG_EXP_HST            |     1 |    36 |     3 |       |       |
    |   7 |        NESTED LOOPS                    |                               |     1 |   159 |  8633 |       |       |
    |   8 |         NESTED LOOPS                   |                               |  8959 |  1076K|  4900 |       |       |
    |   9 |          BUFFER SORT                   |                               |       |       |       |       |       |
    |  10 |           PX RECEIVE                   |                               |       |       |       |       |       |
    |  11 |            PX SEND BROADCAST           | :TQ10000                      |       |       |       |       |       |
    |  12 |             PARTITION RANGE ITERATOR   |                               |     1 |    56 |  4746 |   KEY |    14 |
    |* 13 |              TABLE ACCESS FULL         | META_DR_QRY_LOG_TGT_ALL_HST   |     1 |    56 |  4746 |   KEY |    14 |
    |  14 |          PX BLOCK ITERATOR             |                               |  8959 |   586K|   154 |   KEY |   KEY |
    |* 15 |           TABLE ACCESS FULL            | META_DBQL_TABLE_USAGE_EXP_HST |  8959 |   586K|   154 |   KEY |   KEY |
    |  16 |         PARTITION RANGE ITERATOR       |                               |     1 |       |     2 |   KEY |   KEY |
    |* 17 |          INDEX RANGE SCAN              | DR_QRY_LOG_EXP_HST_IDX        |     1 |       |     2 |   KEY |   KEY |
    ------------------------------------------------------------------------------------------------------------------------
    Predicate Information (identified by operation id):
    ---------------------------------------------------
       6 - filter("RL"."STATEMENT_ID"="TGT"."STATEMENT_ID" AND "RL"."SYSTEMNAME"="TGT"."SYSTEMNAME" AND "SRC"."SYSTEMNAME"="RL"."SYSTEMNAME")
      13 - filter(UPPER("TGT"."SYSTEMNAME")='MOZART' AND UPPER("TGT"."DATABASENAME")='GDW_TABLES' AND
                  UPPER("TGT"."TABLENAME")='SSA_SLNG_LSTG_MTRC_SD' AND "TGT"."ACCTSTRINGDATE">SYSDATE@!-30 AND "TGT"."SYSTEMNAME" IS NOT NULL
                  "TGT"."DATABASENAME" IS NOT NULL AND "TGT"."TABLENAME" IS NOT NULL)
      15 - filter("SRC"."ACCTSTRINGDATE"="TGT"."ACCTSTRINGDATE" AND (UPPER("TGT"."SYSTEMNAME")<>UPPER("SRC"."SYSTEMNAME") OR
                  UPPER("TGT"."DATABASENAME")<>UPPER("SRC"."DATABASENAME") OR UPPER("TGT"."TABLENAME")<>UPPER("SRC"."TABLENAME")) AND
                  "SRC"."ACCTSTRINGDATE">SYSDATE@!-30)
      17 - access("SRC"."QUERYID"="RL"."QUERYID" AND "SRC"."ACCTSTRINGDATE"="RL"."ACCTSTRINGDATE")
           filter("RL"."ACCTSTRINGDATE">SYSDATE@!-30)


    定位問題
    從 上面執行計劃中的表連接方式可以知道,這三個表之間進行了兩次NESTED LOOP,問題出現在最裡層的NESTED LOOP(對兩個表都做了TABLE FULL SCAN),因為表都是百萬級別的(即時過濾後的數據量也不小),性能問題就出現在內表(即被驅動 表)META_DBQL_TABLE_USAGE_EXP_HST做了太多次的全表掃描。如果能把全表掃描轉換成索引,則性能可以大幅度提高。

    下面是NESTED LOOP的介紹:
    嵌套連接把要處理的數據集分為外部循環(驅動數據源)和內部循環(被驅動數據源),外部循環只執行一次,內部循環執行的次數等於外部循環執行返回的數據個數。
    這種連接的好處是內存使用非常少。
    如果驅動數據源有限,且被驅動表在連接列上有相應的索引,則這種連接方式才是高效的。

    下面是這三個表上索引的情況:

    復制代碼 代碼如下:
    SQL> select index_name, table_name from user_indexes where table_name in ('DR_QRY_LOG_EXP_HST',upper('meta_dbql_table_usage_exp_hst'), upper('meta_dr_qry_log_tgt_all_hs
    INDEX_NAME                                                   TABLE_NAME
    ------------------------------------------------------------ ------------------------------------------------------------
    META_DR_QRY_LOG_TGT_ALL_IDX                                  META_DR_QRY_LOG_TGT_ALL_HST
    META_DBQL_TUSAGE_EHST_IDX                                    META_DBQL_TABLE_USAGE_EXP_HST
    DR_QRY_LOG_EXP_HST_IDX                                       DR_QRY_LOG_EXP_HST
    CREATE INDEX "GV"."META_DR_QRY_LOG_TGT_ALL_IDX" ON "GV"."META_DR_QRY_LOG_TGT_ALL_HST" ("STATEMENT_ID", "ACCTSTRINGDATE")
    CREATE INDEX "GV"."META_DBQL_TUSAGE_EHST_IDX" ON "GV"."META_DBQL_TABLE_USAGE_EXP_HST" ("QUERYID", "ACCTSTRINGDATE")
    CREATE INDEX "GV"."DR_QRY_LOG_EXP_HST_IDX" ON "GV"."DR_QRY_LOG_EXP_HST" ("QUERYID", "ACCTSTRINGDATE")


    這 三個索引都是本地分區索引(都包含分區鍵acctstringdate),很顯然,DR_QRY_LOG_EXP_HST表少了個索引,因為它與表 meta_dr_qry_log_tgt_all_hst 在statement_id上做join,因此應該在它的statement_id上也創建本地分區索引如下:

    復制代碼 代碼如下:
    create index DR_QRY_LOG_EXP_HST_IDX2 on gv.DR_QRY_LOG_EXP_HST (statement_id,ACCTSTRINGDATE) local;


    性能對比
    新的執行計劃如下:

    復制代碼 代碼如下:
    ------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                              | Name                          | Rows  | Bytes | Cost  | Pstart| Pstop |
    ------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                       |                               |     1 |   159 |  4838 |       |       |
    |   1 |  SORT UNIQUE                           |                               |     1 |   159 |  4838 |       |       |
    |*  2 |   TABLE ACCESS BY LOCAL INDEX ROWID    | META_DBQL_TABLE_USAGE_EXP_HST |     1 |    67 |     3 |       |       |
    |   3 |    NESTED LOOPS                        |                               |     1 |   159 |  4816 |       |       |
    |   4 |     NESTED LOOPS                       |                               |    18 |  1656 |  4762 |       |       |
    |   5 |      PARTITION RANGE ITERATOR          |                               |     1 |    56 |  4746 |   KEY |    14 |
    |*  6 |       TABLE ACCESS FULL                | META_DR_QRY_LOG_TGT_ALL_HST   |     1 |    56 |  4746 |   KEY |    14 |
    |   7 |      PARTITION RANGE ITERATOR          |                               |    18 |   648 |    16 |   KEY |    14 |
    |*  8 |       TABLE ACCESS BY LOCAL INDEX ROWID| DR_QRY_LOG_EXP_HST            |    18 |   648 |    16 |   KEY |    14 |
    |*  9 |        <STRONG>INDEX RANGE SCAN                | DR_QRY_LOG_EXP_HST_IDX2</STRONG>       |    31 |       |    15 |   KEY |    14 |
    |  10 |     PARTITION RANGE ITERATOR           |                               |     1 |       |     2 |   KEY |   KEY |
    |* 11 |      INDEX RANGE SCAN                  | META_DBQL_TUSAGE_EHST_IDX     |     1 |       |     2 |   KEY |   KEY |
    ------------------------------------------------------------------------------------------------------------------------
    Predicate Information (identified by operation id):
    ---------------------------------------------------
       2 - filter((UPPER("TGT"."SYSTEMNAME")<>UPPER("SRC"."SYSTEMNAME") OR
                  UPPER("TGT"."DATABASENAME")<>UPPER("SRC"."DATABASENAME") OR UPPER("TGT"."TABLENAME")<>UPPER("SRC"."TABLENAME"))
                  AND "SRC"."SYSTEMNAME"="RL"."SYSTEMNAME")
       6 - filter(UPPER("TGT"."SYSTEMNAME")='MOZART' AND UPPER("TGT"."DATABASENAME")='GDW_TABLES' AND
                  UPPER("TGT"."TABLENAME")='SSA_SLNG_LSTG_MTRC_SD' AND "TGT"."ACCTSTRINGDATE">SYSDATE@!-30 AND "TGT"."SYSTEMNAME"
                  IS NOT NULL AND "TGT"."DATABASENAME" IS NOT NULL AND "TGT"."TABLENAME" IS NOT NULL)
       8 - filter("RL"."SYSTEMNAME"="TGT"."SYSTEMNAME")
       9 - access("RL"."STATEMENT_ID"="TGT"."STATEMENT_ID" AND "RL"."ACCTSTRINGDATE">SYSDATE@!-30 AND
                  "RL"."ACCTSTRINGDATE" IS NOT NULL)
      11 - access("SRC"."QUERYID"="RL"."QUERYID" AND "SRC"."ACCTSTRINGDATE"="RL"."ACCTSTRINGDATE")
           filter("SRC"."ACCTSTRINGDATE"="TGT"."ACCTSTRINGDATE" AND "SRC"."ACCTSTRINGDATE">SYSDATE@!-30)


    從新的的執行計劃可以看出,它的第一個NESTED LOOP果然用了最新創建的索引。
    下面是執行時間:

    復制代碼 代碼如下:
    已用


    兩秒種搞定,遠遠超出他期望的5s :)
    方法總結
    NESTED LOOP高效的條件:驅動數據源有限,且被驅動表在連接列上有相應的索引。

    1. 上一頁:
    2. 下一頁:
    Copyright © 程式師世界 All Rights Reserved