1. 程式人生 > >優化案例4----錯誤的大表走HASH+並行----->正確的走Nested loop

優化案例4----錯誤的大表走HASH+並行----->正確的走Nested loop

       談到大表與大表之間的連線方式,總會下意識的選擇走HASH JOIN+並行效率才高,其實具體情況必須具體問題,在有的時候,大表走巢狀迴圈的效率要優於走HASH JOIN,下面就用我之前優化過的一個案例來說明。

      一個哥們找我優化一個SQL  跑的很慢,103萬個邏輯讀,103萬個物理讀,要跑19秒才能跑完。

     下面是它的SQL 以及執行計劃:

SELECT "A1"."SERV_ID"
   ,"A1"."ATTR_ID"
   ,"A1"."ATTR_VAL"
   ,"A1"."EFF_DATE"
   ,NVL("A1"."EXP_DATE", TO_DATE(NULL, NULL))
   ,"A2"."OP_SEQ"
   ,"A2"."OP_TYPE"
FROM COMM."USER_INFO_OPLOG_MASTER2" "A2"
   ,COMM."SERV_ATTR" "A1"
WHERE "A2"."MEMOP_DATE" IS NULL
   AND "A2"."TABLE_NAME" = 'SERV_ATTR'
   AND "A1"."SERV_ID" = "A2"."TABLE_COLUMN_1"
   AND "A1"."AGREEMENT_ID" = "A2"."TABLE_COLUMN_2"
   AND "A1"."ATTR_ID" = "A2"."TABLE_COLUMN_3"
ORDER BY "A2"."OP_SEQ"

563 rows selected.

Elapsed: 00:00:19.50

Execution Plan
----------------------------------------------------------
Plan hash value: 669020553

---------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name                        | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |    TQ  |IN-OUT| PQ Distrib |
---------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                             |   150K|    17M|       | 61528   (3)| 00:12:19 |        |      |            |
|   1 |  PX COORDINATOR                      |                             |       |       |       |            |          |        |      |            |
|   2 |   PX SEND QC (ORDER)                 | :TQ10002                    |   150K|    17M|       | 61528   (3)| 00:12:19 |  Q1,02 | P->S | QC (ORDER) |
|   3 |    SORT ORDER BY                     |                             |   150K|    17M|    20M| 61528   (3)| 00:12:19 |  Q1,02 | PCWP |            |
|   4 |     PX RECEIVE                       |                             |   150K|    17M|       | 61526   (3)| 00:12:19 |  Q1,02 | PCWP |            |
|   5 |      PX SEND RANGE                   | :TQ10001                    |   150K|    17M|       | 61526   (3)| 00:12:19 |  Q1,01 | P->P | RANGE      |
|*  6 |       HASH JOIN                      |                             |   150K|    17M|       | 61526   (3)| 00:12:19 |  Q1,01 | PCWP |            |
|   7 |        BUFFER SORT                   |                             |       |       |       |            |          |  Q1,01 | PCWC |            |
|   8 |         PX RECEIVE                   |                             |   147K|    11M|       |  1069   (1)| 00:00:13 |  Q1,01 | PCWP |            |
|   9 |          PX SEND BROADCAST           | :TQ10000                    |   147K|    11M|       |  1069   (1)| 00:00:13 |        | S->P | BROADCAST  |
|  10 |           TABLE ACCESS BY INDEX ROWID| USER_INFO_OPLOG_MASTER2     |   147K|    11M|       |  1069   (1)| 00:00:13 |        |      |            |
|* 11 |            INDEX RANGE SCAN          | IDX_INFO_OPLOG_M2COMB_11502 |  6113 |       |       |    33   (0)| 00:00:01 |        |      |            |
|  12 |        PX BLOCK ITERATOR             |                             |   229M|  9427M|       | 60101   (2)| 00:12:02 |  Q1,01 | PCWC |            |
|* 13 |         TABLE ACCESS FULL            | SERV_ATTR                   |   229M|  9427M|       | 60101   (2)| 00:12:02 |  Q1,01 | PCWP |            |
---------------------------------------------------------------------------------------------------------------------------------------------------------

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

   6 - access("A1"."SERV_ID"="A2"."TABLE_COLUMN_1" AND "A1"."AGREEMENT_ID"="A2"."TABLE_COLUMN_2" AND "A1"."ATTR_ID"="A2"."TABLE_COLUMN_3")
  11 - access("A2"."MEMOP_DATE" IS NULL AND "A2"."TABLE_NAME"='SERV_ATTR')
       filter("A2"."TABLE_NAME"='SERV_ATTR')
  13 - filter(SYS_OP_BLOOM_FILTER(:BF0000,"A1"."SERV_ID","A1"."AGREEMENT_ID","A1"."ATTR_ID"))

Note
-----
   - dynamic sampling used for this statement (level=6)


Statistics
----------------------------------------------------------
         30  recursive calls
          0  db block gets
    1039308  consistent gets
    1003269  physical reads
        716  redo size
      31187  bytes sent via SQL*Net to client
        927  bytes received via SQL*Net from client
         39  SQL*Net roundtrips to/from client
         11  sorts (memory)
          0  sorts (disk)
        563  rows processed

    從執行計劃來看 有大概3個問題:

<1> 原SQL並未用hint paraller 提示走並行,但是從實際的執行計劃來看,還是走了並行,應該是這兩張表上設定了預設的並行度。一般可以在資料倉庫裡面這樣搞,但是問了一下這個哥們,他們的資料庫系統是OLTP和OLAP混合系統,因此不建議在表上設定較高的並行度,這樣會促使CBO更加傾向於選擇走並行+全表掃描。

 <2> ID=13 的謂詞資訊有了“(SYS_OP_BLOOM_FILTER(:BF0000,"A1"."SERV_ID","A1"."AGREEMENT_ID","A1"."ATTR_ID"))” ,從10g R2 以後 CBO 優化器演算法加入了布隆過濾演算法,一般在兩表走HASH JOIN+並行的時候會在HASH JOIN的被驅動表中出現,但是從我之前優化過SQL的經驗來看,這個新特性帶了的BUG較多,尤其在被驅動表資料量特別大的時候使用該演算法影響會更大,因此,建議資料系統解決 將該引數disable掉。

<3>  可以看到這個SQL最終返回的結果只有563行,由此推斷,走Nested loop 巢狀迴圈應該會比有HASH 效率更高效。

下面我通過以下SQL 印證了我的判斷:

A2 ------USER_INFO_OPLOG_MASTER2  這個表的總資料量為:67萬條

A1-------SERV_ATTR            這個表的總資料量為:2億多條

執行SQL:

select count(*) from COMM.USER_INFO_OPLOG_MASTER2 where MEMOP_DATE is null and TABLE_NAME='SERV_ATTR';

結果為:    

COUNT(*)
----------
     53677

A2 這個表過濾以後只返回5萬多條

再來看A1 表中索引的資訊:

****************************************************************************************
INDEX INFO
****************************************************************************************

TABLE           TABLE                               Index                                    COLUMN                     Col
OWNER           NAME                                Name                           Unique    NAME                       Pos DESC
--------------- ----------------------------------- ------------------------------ --------- ------------------------- ---- ----
COMM            SERV_ATTR                           IDX_SERV_ATTR_AGREEMENT_ID     NONUNIQUE AGREEMENT_ID                 1 ASC
                                                    IDX_SERV_ATTR_ATTR_ID          NONUNIQUE ATTR_ID                      1 ASC
                                                    IDX_SERV_ATTR_INTERNET         NONUNIQUE ATTR_VAL                     1 ASC
                                                                                             ATTR_ID                      2 ASC
                                                    IDX_SERV_ATTR_SERV_ID          NONUNIQUE SERV_ID                      1 ASC
                                                    IDX_SERV_ATTR_VAL8             NONUNIQUE ATTR_VAL                     1 ASC
                                                    PK_SERV_ATTR                   UNIQUE    SERV_ID                      1 ASC
                                                                                             AGREEMENT_ID                 2 ASC
                                                                                             ATTR_ID                      3 ASC


A1表有個聯合主鍵 建立在了:SERV_ID   AGREEMENT_ID    ATTR_ID  這三列上,而看SQL 這三列恰恰是A1與A2的連線列,那就好辦了 ,我讓A2做巢狀迴圈的驅動表,A1 做被驅動表,那麼A1 一定走的是通過主鍵PK_SERV_ATTR 走索引唯一掃描然後回表,效能不會太低。

我讓它加下以下的HINT 然後去set autotrace traceonly  去看執行計劃和邏輯讀:

/*+ use_nl(A1,A2)  index(A1,PK_SERV_ATTR) leading(A2)  */

Elapsed: 00:00:05.45

Execution Plan
----------------------------------------------------------
Plan hash value: 4263321111

----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name                        | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                             |   142K|    16M|       |   286K  (1)| 00:57:13 |
|   1 |  SORT ORDER BY                 |                             |   142K|    16M|    19M|   286K  (1)| 00:57:13 |
|   2 |   NESTED LOOPS                 |                             |   142K|    16M|       |   282K  (1)| 00:56:26 |
|   3 |    NESTED LOOPS                |                             |   142K|    16M|       |   282K  (1)| 00:56:26 |
|   4 |     TABLE ACCESS BY INDEX ROWID| USER_INFO_OPLOG_MASTER2     |   140K|    10M|       |  1489   (1)| 00:00:18 |
|*  5 |      INDEX RANGE SCAN          | IDX_INFO_OPLOG_M2COMB_11502 |  9438 |       |       |    49   (0)| 00:00:01 |
|*  6 |     INDEX UNIQUE SCAN          | PK_SERV_ATTR                |     1 |       |       |     2   (0)| 00:00:01 |
|   7 |    TABLE ACCESS BY INDEX ROWID | SERV_ATTR                   |     1 |    43 |       |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------------

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

   5 - access("A2"."TABLE_NAME"='SERV_ATTR' AND "A2"."MEMOP_DATE" IS NULL)
   6 - access("A1"."SERV_ID"="A2"."TABLE_COLUMN_1" AND "A1"."AGREEMENT_ID"="A2"."TABLE_COLUMN_2" AND
              "A1"."ATTR_ID"="A2"."TABLE_COLUMN_3")

Note
-----
   - dynamic sampling used for this statement (level=6)


Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
      60044  consistent gets
       5440  physical reads
       8824  redo size
      19160  bytes sent via SQL*Net to client
        762  bytes received via SQL*Net from client
         24  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
        345  rows processed

最終5秒就跑完了,邏輯讀從之前的103萬減少到了6萬。