對Hash Join的一次優化
轉自http://www.itpub.net/thread-955209-1-1.html
對Hash Join的一次優化
前兩天解決了一個優化SQL的case,SQL語句如下,big_table為150G大小,small_table很小,9000多條記錄,不到1M大小
hash_area_size, sort_area_size均設定足夠大,可以進行optimal hash join和memory sort
select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where (a.category = b.from_cat or
a.category2 = b.from_cat) and
a.site_id = b.site_id and
a.sale_end >= sysdate;
根據經驗來看,整個SQL執行的時間應該和FTS BIG_TABLE的時間差不多
但是FTS BIG_TABLE的時間大約是8分鐘,而真個SQL執行的時間長達3~4小時
那麼問題究竟出在哪裡?
FTS時間應該不會有太大變化,那麼問題應該在hash join,設定event來trace一下hash join的過程。
SQL> alter session set events '10104 trace name context forever, level 2';
Session altered.
select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where (a.category = b.from_cat or
a.category2 = b.from_cat) and
a.site_id = b.site_id and
a.sale_end >= sysdate;
從trace file中Hash Table中這一段找出了問題所在:
### Hash table ###
# NOTE: The calculated number of rows in non-empty buckets may be smaller
# than the true number.
Number of buckets with 0 rows: 16373
Number of buckets with 1 rows: 0
Number of buckets with 2 rows: 0
Number of buckets with 3 rows: 1
Number of buckets with 4 rows: 0
Number of buckets with 5 rows: 0
Number of buckets with 6 rows: 0
Number of buckets with 7 rows: 1
Number of buckets with 8 rows: 0
Number of buckets with 9 rows: 0
Number of buckets with between 10 and 19 rows: 1
Number of buckets with between 20 and 29 rows: 1
Number of buckets with between 30 and 39 rows: 3
Number of buckets with between 40 and 49 rows: 0
Number of buckets with between 50 and 59 rows: 0
Number of buckets with between 60 and 69 rows: 0
Number of buckets with between 70 and 79 rows: 0
Number of buckets with between 80 and 89 rows: 0
Number of buckets with between 90 and 99 rows: 0
Number of buckets with 100 or more rows: 4
### Hash table overall statistics ###
Total buckets: 16384 Empty buckets: 16373 Non-empty buckets: 11
Total number of rows: 9232
Maximum number of rows in a bucket: 2531
Average number of rows in non-empty buckets: 839.272705
仔細看,在一個bucket中最多的行數竟然有2531行,因為bucket中是一個連結串列的結構,所以這幾千行都是串在一個連結串列上。
由這一點想到這個Hash Table所依賴的hash key的distinct value可能太少,重複值太多。否則不應該會有這麼多行在同一個bucket裡面。
因為Join條件裡面有兩個列from_cat和site_id,窮舉法有三種情況
1. Build hash table based on (from_cat,site_id):
SQL> select site_id,from_cat,count(*) from SMALL_TABLE group by site_id,from_cat having count(*)>100;
no rows selected
2. Build hash table based on (from_cat):
SQL> select from_cat,count(*) from SMALL_TABLE group by from_cat having count(*)>100;
no rows selected
3. Build hash table based on (site_id):
SQL> select site_id,count(*) from SMALL_TABLE group by site_id having count(*)>100;
SITE_ID COUNT(*)
---------- ----------
0 2531
2 2527
146 1490
210 2526
到這裡可以發現,基於site_id這種情況和trace file中這兩行很相符:
Number of buckets with 100 or more rows: 4
Maximum number of rows in a bucket: 2531
所以推斷這個hash table是基於site_id而建的,而Big_Table中大量的行site_id=0,都落在這個linked list最長的bucket中.
而大部分行都會掃描完整個連結串列而最後被丟棄掉,所以這個Hash Join的操作效率非常差,幾乎變為了Nest Loop操作
找到了根本原因,問題也就迎刃而解了。
理想狀況下,hash table應當建立於(site_id,from_cat)上,那麼問題肯定出在這個OR上,把OR用UNION改寫
select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where a.category = b.from_cat and
a.site_id = b.site_id and
a.sale_end >= sysdate
UNION
select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where a.category2 = b.from_cat and
a.site_id = b.site_id and
a.sale_end >= sysdate;
### Hash table ###
# NOTE: The calculated number of rows in non-empty buckets may be smaller
# than the true number.
Number of buckets with 0 rows: 9306
Number of buckets with 1 rows: 5310
Number of buckets with 2 rows: 1436
Number of buckets with 3 rows: 285
Number of buckets with 4 rows: 43
Number of buckets with 5 rows: 4
Number of buckets with 6 rows: 0
Number of buckets with 7 rows: 0
Number of buckets with 8 rows: 0
Number of buckets with 9 rows: 0
Number of buckets with between 10 and 19 rows: 0
Number of buckets with between 20 and 29 rows: 0
Number of buckets with between 30 and 39 rows: 0
Number of buckets with between 40 and 49 rows: 0
Number of buckets with between 50 and 59 rows: 0
Number of buckets with between 60 and 69 rows: 0
Number of buckets with between 70 and 79 rows: 0
Number of buckets with between 80 and 89 rows: 0
Number of buckets with between 90 and 99 rows: 0
Number of buckets with 100 or more rows: 0
### Hash table overall statistics ###
Total buckets: 16384 Empty buckets: 9306 Non-empty buckets: 7078
Total number of rows: 9232
Maximum number of rows in a bucket: 5
Average number of rows in non-empty buckets: 1.304323
這就是我們所需要的Hash Table,最長的連結串列只有五行資料
整個SQL的執行時間從三四個小時縮短為16分鐘,大大超出了developer的預期
這個SQL單純從PLAN上很難看出問題所在,需要了解Hash Join的機制,進行更深一步的分析
hash_area_size, sort_area_size均設定足夠大,可以進行optimal hash join和memory sort
select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where (a.category = b.from_cat or
a.category2 = b.from_cat) and
a.site_id = b.site_id and
a.sale_end >= sysdate;
粗略來看,PLAN非常的完美,SQL HINT寫的也很到位,小表在內build hash table,大表在外進行probe操作,PHP code:
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 174 | 18 (17)|
| 1 | SORT UNIQUE | | 2 | 174 | 18 (17)|
|* 2 | HASH JOIN | | 2 | 174 | 17 (12)|
| 3 | TABLE ACCESS FULL | SMALL_TABLE | 1879 | 48854 | 14 (8)|
|* 4 | TABLE ACCESS FULL | BIG_TABLE | 4 | 244 | 3 (34)|
--------------------------------------------------------------------------
根據經驗來看,整個SQL執行的時間應該和FTS BIG_TABLE的時間差不多
但是FTS BIG_TABLE的時間大約是8分鐘,而真個SQL執行的時間長達3~4小時
那麼問題究竟出在哪裡?
FTS時間應該不會有太大變化,那麼問題應該在hash join,設定event來trace一下hash join的過程。
SQL> alter session set events '10104 trace name context forever, level 2';
Session altered.
select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where (a.category = b.from_cat or
a.category2 = b.from_cat) and
a.site_id = b.site_id and
a.sale_end >= sysdate;
從trace file中Hash Table中這一段找出了問題所在:
### Hash table ###
# NOTE: The calculated number of rows in non-empty buckets may be smaller
# than the true number.
Number of buckets with 0 rows: 16373
Number of buckets with 1 rows: 0
Number of buckets with 2 rows: 0
Number of buckets with 3 rows: 1
Number of buckets with 4 rows: 0
Number of buckets with 5 rows: 0
Number of buckets with 6 rows: 0
Number of buckets with 7 rows: 1
Number of buckets with 8 rows: 0
Number of buckets with 9 rows: 0
Number of buckets with between 10 and 19 rows: 1
Number of buckets with between 20 and 29 rows: 1
Number of buckets with between 30 and 39 rows: 3
Number of buckets with between 40 and 49 rows: 0
Number of buckets with between 50 and 59 rows: 0
Number of buckets with between 60 and 69 rows: 0
Number of buckets with between 70 and 79 rows: 0
Number of buckets with between 80 and 89 rows: 0
Number of buckets with between 90 and 99 rows: 0
Number of buckets with 100 or more rows: 4
### Hash table overall statistics ###
Total buckets: 16384 Empty buckets: 16373 Non-empty buckets: 11
Total number of rows: 9232
Maximum number of rows in a bucket: 2531
Average number of rows in non-empty buckets: 839.272705
仔細看,在一個bucket中最多的行數竟然有2531行,因為bucket中是一個連結串列的結構,所以這幾千行都是串在一個連結串列上。
由這一點想到這個Hash Table所依賴的hash key的distinct value可能太少,重複值太多。否則不應該會有這麼多行在同一個bucket裡面。
因為Join條件裡面有兩個列from_cat和site_id,窮舉法有三種情況
1. Build hash table based on (from_cat,site_id):
SQL> select site_id,from_cat,count(*) from SMALL_TABLE group by site_id,from_cat having count(*)>100;
no rows selected
2. Build hash table based on (from_cat):
SQL> select from_cat,count(*) from SMALL_TABLE group by from_cat having count(*)>100;
no rows selected
3. Build hash table based on (site_id):
SQL> select site_id,count(*) from SMALL_TABLE group by site_id having count(*)>100;
SITE_ID COUNT(*)
---------- ----------
0 2531
2 2527
146 1490
210 2526
到這裡可以發現,基於site_id這種情況和trace file中這兩行很相符:
Number of buckets with 100 or more rows: 4
Maximum number of rows in a bucket: 2531
所以推斷這個hash table是基於site_id而建的,而Big_Table中大量的行site_id=0,都落在這個linked list最長的bucket中.
而大部分行都會掃描完整個連結串列而最後被丟棄掉,所以這個Hash Join的操作效率非常差,幾乎變為了Nest Loop操作
找到了根本原因,問題也就迎刃而解了。
理想狀況下,hash table應當建立於(site_id,from_cat)上,那麼問題肯定出在這個OR上,把OR用UNION改寫
select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where a.category = b.from_cat and
a.site_id = b.site_id and
a.sale_end >= sysdate
UNION
select /*+ leading(b) use_hash(a b) */ distinct a.ID
from BIG_TABLE a, SMALL_TABLE b
where a.category2 = b.from_cat and
a.site_id = b.site_id and
a.sale_end >= sysdate;
初看這個PLAN好像不如第一個PLAN,因為執行了兩次BIG_TABLE的FTS,但是讓我們在來看看HASH TABLE的結構PHP code:
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 148 | 36 (59)|
| 1 | SORT UNIQUE | | 2 | 148 | 36 (59)|
| 2 | UNION-ALL | | | | |
|* 3 | HASH JOIN | | 1 | 74 | 17 (12)|
| 4 | TABLE ACCESS FULL| SMALL_TABLE | 1879 | 48854 | 14 (8)|
|* 5 | TABLE ACCESS FULL| BIG_TABLE | 4 | 192 | 3 (34)|
|* 6 | HASH JOIN | | 1 | 74 | 17 (12)|
| 7 | TABLE ACCESS FULL| SMALL_TABLE | 1879 | 48854 | 14 (8)|
|* 8 | TABLE ACCESS FULL| BIG_TABLE | 4 | 192 | 3 (34)|
--------------------------------------------------------------------------
### Hash table ###
# NOTE: The calculated number of rows in non-empty buckets may be smaller
# than the true number.
Number of buckets with 0 rows: 9306
Number of buckets with 1 rows: 5310
Number of buckets with 2 rows: 1436
Number of buckets with 3 rows: 285
Number of buckets with 4 rows: 43
Number of buckets with 5 rows: 4
Number of buckets with 6 rows: 0
Number of buckets with 7 rows: 0
Number of buckets with 8 rows: 0
Number of buckets with 9 rows: 0
Number of buckets with between 10 and 19 rows: 0
Number of buckets with between 20 and 29 rows: 0
Number of buckets with between 30 and 39 rows: 0
Number of buckets with between 40 and 49 rows: 0
Number of buckets with between 50 and 59 rows: 0
Number of buckets with between 60 and 69 rows: 0
Number of buckets with between 70 and 79 rows: 0
Number of buckets with between 80 and 89 rows: 0
Number of buckets with between 90 and 99 rows: 0
Number of buckets with 100 or more rows: 0
### Hash table overall statistics ###
Total buckets: 16384 Empty buckets: 9306 Non-empty buckets: 7078
Total number of rows: 9232
Maximum number of rows in a bucket: 5
Average number of rows in non-empty buckets: 1.304323
這就是我們所需要的Hash Table,最長的連結串列只有五行資料
整個SQL的執行時間從三四個小時縮短為16分鐘,大大超出了developer的預期
這個SQL單純從PLAN上很難看出問題所在,需要了解Hash Join的機制,進行更深一步的分析
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-668912/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 一次HASH JOIN 臨時表空間不足的分析和優化思路優化
- 一次HASH JION過慢優化(2)優化
- 一次HASH JION過慢優化(1)優化
- mysql的left join和inner join的效率對比,以及如何優化MySql優化
- ORACLE Hash JoinOracle
- oralce之 10046對Hash Join分析
- Mysql join 的本質,Nested Loop Join 和 Hash JoinMySqlOOP
- 記錄一次 postgresql 最佳化案例( 巢狀迴圈改HASH JOIN )SQL巢狀
- 【sql調優之執行計劃】hash joinSQL
- HASH JOIN ,MERGE JOIN ,NESTED LOOPOOP
- hash join\nest loop join\sort merge join的實驗OOP
- HASH join詳解
- HASH JOIN ,MERGE JOIN ,NESTED LOOP的比較OOP
- nested loop,sort merge join,hash joinOOP
- join 查詢優化優化
- Oracle中的Hash Join祥解Oracle
- Oracle 的 hash join連線方式Oracle
- Oracle中的Hash Join詳解Oracle
- 記一次ALTER SESSION SET hash_join_enabled specifies an obsolete parameterSession
- HASH JOIN ,MERGE JOIN ,NESTED LOOP(R2)OOP
- Oracle中的Hash Join詳解 ztOracle
- Hash join演算法原理演算法
- MySQL系列6 - join語句的優化MySql優化
- sql server中的hash應用優化SQLServer優化
- 表的連線方式:NESTED LOOP、HASH JOIN、SORT MERGE JOIN(轉)OOP
- hash join構建點陣圖的理解
- mysql left join 優化學習MySql優化
- Hash join演算法原理(轉)演算法
- oracle hash join演算法原理Oracle演算法
- Oracle中的Hash Join祥解(R2)Oracle
- oracle hash join原理及注意事項Oracle
- 多表連線的三種方式詳解 hash join、merge join、 nested loopOOP
- 對Join的理解
- NOT IN 一次優化優化
- 一次優化優化
- nested loops 和hash join的一點測試OOP
- 通過swap_join_inputs來控制多表連線hash join中的build tableUI
- mysql update join優化update in查詢效率MySql優化