優化案例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萬。