Oracle表连接深入浅出
表的連接
表的連接是指在一個SQL語句中通過表與表之間的關聯,從一個或多個表檢索出相關的數據。如果一個SQL語句的關聯表超過兩個,
那么連接的順序如何呢?ORACLE首先連接其中的兩個表,產生一個結果集;然后將產生的結果集與下一個表再進行關聯;繼續這
個過程,直到所有的表都連接完成;最后產生所需的數據。
ORACLE 從6的版本開始,優化器使用4種不同的表的連接方式:
| ? |
1 嵌套循環連接(NESTED LOOP JOIN)
2 群集連接 (CLUSTER JOIN)
3 排序合并連接(SORT MERGE JOIN)
4 笛卡爾連接 (CARTESIAN JOIN)
ORACLE 7.3中,新增加了
5 哈希連接(HASH JOIN)。
在ORACLE 8中,新增加了
6 索引連接(INDEX JOIN)。
這六種連接方式都有其各自技術特點,在一定的條件下,可以充分發揮高效的性能。但是也都有其局限性,如果使用不當,不僅不
能提高效率,反而會嚴重影響系統的性能。因此,深入地探討連接方式的內部 運行機制對于性能優化是必要的。
1 嵌套循環連接
嵌套循環連接的內部處理的流程:
1) Oracle 優化器根據基于規則RBO或基于成本CBO的原則,選擇兩個表中的一個作為驅動表,并指定其為外部表。
2) Oracle 優化器再將另外一個表指定為內部表。
3) Oracle從外部表中讀取第一行,然后和內部表中的數據逐一進行對比,所有匹配的記錄放在結果集中。
4) Oracle讀取外部表中的第二行,再和內部表中的數據逐一進行對比,所有匹配的記錄添加到結果集中。
5) 重復上述步驟,直到外部表中的所有紀錄全部處理完。
6) 最后產生滿足要求的結果集。
使用嵌套循環連接是一種從結果集中提取第一批記錄最快速的方法。在驅動行源表(就是正在查找的記錄,外部表)較小、或者內部行
源表已連接的列有惟一的索引或高度可選的非惟一索引時, 嵌套循環連接效果是比較理想的。嵌套循環連接比其他連接方法有優勢,它
可以快速地從結果集中提取第一批記錄,而不用等待整個結果集完全確定下來。這樣,在理想情況下,終端用戶就可以通過查詢屏幕查看
第一批記錄,而在同時讀取其他記錄。不管如何定義連接的條件或者模式,任何兩個行記錄源都可以使用嵌套循環連接,所以嵌套循環連
接是非常靈活的。
然而,
如果內部行源表(讀取的第二張表)已連接的列上不包含索引,或者索引不是高度可選時,嵌套循環連接效率是很低的。
如果驅動表的記錄非常龐大時,其他的連接方法可能更加有效。
可以通過在SQL語句中添加HINTS(use_nl),強制ORACLE優化器產生嵌套循環連接的執行計劃。
2 群集連接(CLUSTER JOIN)
群集連接實際上是嵌套循環連接的一種特例。如果所連接的兩張源表是群集中的表,即兩張表屬于同一個段(SEGMENT),
那么ORACLE能夠使用群集連接。處理的過程是:ORACLE從第一張行源表中讀取第一行,然后在第二張行源表中使用CLUSTER索
引查找能夠匹配到的紀錄;繼續上面的步驟處理行源表中的第二行,直到所有的記錄全部處理完。
| ? |
群集連接的效率極高,因為兩個參加連接的行源表實際上處于同一個物理塊上。但是,群集連接也有其限制,沒有群集的兩
個表不可能用群集連接。所以,群集連接實際上很少使用。
3 排序合并連接(SORT MERGE JOIN)
排序合并連接內部處理的流程:
1) 優化器判斷第一個行源表是否已經排序,如果已經排序,則到第3步,否則到第2步。
2) 第一個源表排序
3) 優化器判斷第二個行源表是否已經排序,如果已經排序,則到第5步,否則到第4步。
4) 第二個源表排序
5) 已經排過序的兩個源表進行合并操作,并生成最終的結果集。
在缺乏數據的選擇性或者可用的索引時,或者兩個行源表都過于龐大(所選的數據超過表記錄數的5%)時,排序合并連接將比嵌套
循環連更加高效。排列合并連接需要比較大的臨時內存塊,以用于排序,這將導致在臨時表空間占用更多的內存和磁盤I/O。
可以通過在SQL語句中添加HINTS(use_merge),強制ORACLE優化器產生排序合并連接的執行計劃。
4 笛卡爾連接 (CARTESIAN JOIN)
笛卡爾連接是指在sql語句中沒有寫出表連接的條件,優化器把第一個表的每一條記錄和第二個表的所有紀錄相連接。如果第
一個表的紀錄數為m, 第二個表的紀錄數為m,則會產生m*n條紀錄數。
由于笛卡爾連接會導致性能很差的SQL,因此一般也很少用到。
5 哈希連接(Hash Join)
Hash Join 概述
Hash join 算法的一個基本思想就是根據小的row sources(稱作build input我們記較小的表為S ,較大的表為B)建立一個可以
存在于 hash area內存中的hash table ,然后用大的 row sources(稱作probe input) 來探測前面所建的hash table。如果
hash area 內存不夠大, hash table就無法完全存放在hash area內存中。針對這種情況,Oracle在連接鍵利用一個hash函數將
build input 和 probe input 分割成多個不相連的分區(分別記作 Si 和 Bi ),這個階段叫做分區階段;然后各自相應的分區,
即 Si 和 Bi 再做 Hash join ,這個階段叫做 join 階段。
如果在分區后,針對某個分區所建的hash table還是太大的話,oracle就采用nested-loops hash join. 所謂的nested-loops hash join就是對部分 Si 建立 hash table ,然后讀取所有的 Bi 與所建的 hash table 做連接,然后再對剩余的 Si 建立 hash table ,再
將所有的 Bi 與所建的 hash table 做連接,直至所有的 Si 都連接完了。
Hash Join 算法有一個限制,就是它是在假設兩張表在連接鍵上是均勻的,也就是說每個分區擁有差不多的數據。但是實際當中數據都是不
均勻的,為了很好地解決這個問題, oracle 引進了幾種技術,位圖向量過濾、角色互換、柱狀圖,這些術語的具體意義會在后面詳細介紹。
Hash Join 原理
我們用一個例子來解釋 Hash Join 算法的原理,以及上述所提到的術語。
考慮以下兩個數據集。
S={1,1,1,3,3,4,4,4,4,5,8,8,8,8,10}
B={0,0,1,1,1,1,2,2,2,2,2,2,3,8,9,9,9,10,10,11}
oracle優化器根據統計信息,Hash Join 的第一步就是判定小表(即 build input )是否能完全存放在 hash area 內存中。如果能完全存放在內存中,則在內存中建立 hash table ,這是最簡單的 hash join 。
| ? |
如果不能全部存放在內存中,則 build input 必須分區。分區的個數叫做 fan-out 。 Fan-out 是由 hash_area_size 和 cluster size 來決定的。其中 cluster size 等于 db_block_size * hash_multiblock_io_count , hash_multiblock_io_count 在 oracle9i 中是隱含參數。這里需要注意的是 fan-out 并不是 build input 的大小 /hash_ara_size ,也就是說 oracle 決定的分區大小有可能還是不能完全存放在 hash area 內存中。大的 fan-out 導致許多小的分區,影響性能,而小的 fan-out 導致少數的大的分區,以至于每個分區不能全部存放在內存中,這也影響 hash join 的性能。
Oracle 采用內部一個 hash 函數作用于連接鍵上,將 S 和 B 分割成多個分區,在這里我們假設這個 hash 函數為求余函數,即 Mod(join_column_value,10) 。這樣產生十個分區,如下表。
經過這樣的分區之后,只需要相應的分區之間做 join 即可(也就是所謂的 partition pairs ),如果有一個分區為 NULL 的話,則相應的分區 join 即可忽略。
在將 S 表讀入內存分區時,oracle將記錄連接鍵的唯一值,構建成所謂的位圖向量,它需要占 hash area 內存的 5% 左右。在這里即為 {1,3,4,5,8,10} 。
當對 B 表進行分區時,將每一個連接鍵上的值與位圖向量相比較,如果不在其中,則將其記錄丟棄。在我們這個例子中, B 表中以下數據將被丟棄 {0,0,2,2,2,2,2,2,9,9,9,9,9} 。這個過程就是位圖向量過濾。
當 S1,B1 做完連接后,接著對 Si,Bi 進行連接,這里 oracle 將比較兩個分區,選取小的那個做 build input ,就是動態角色互換,這個動態角色互換發生在除第一對分區以外的分區上面。
Hash Join 算法
第 1 步:判定小表是否能夠全部存放在 hash area 內存中,如果可以,則做內存 hash join 。如果不行,轉第二步。
第 2 步:決定 fan-out 數。
(Number of Partitions) * C<= Favm *M
其中 C 為 Cluster size ,
| ? |
其值為 DB_BLOCK_SIZE*HASH_MULTIBLOCK_IO_COUNT ; Favm 為 hash area 內存可以使用的百分比,一般為 0.8 左右; M 為 Hash_area_size 的大小。
第 3 步:讀取部分小表 S ,采用內部 hash 函數 ( 這里稱為 hash_fun_1) ,將連接鍵值映射至某個分區,同時采用 hash_fun_2 函數對連接鍵值產生另外一個 hash 值,這個 hash 值用于創建 hash table 用,并且與連接鍵值存放在一起。
第 4 步:對 build input 建立位圖向量。
第 5 步:如果內存中沒有空間了,則將分區寫至磁盤上。
第 6 步:讀取小表 S 的剩余部分,重復第三步,直至小表 S 全部讀完。
第 7 步:將分區按大小排序,選取幾個分區建立 hash table( 這里選取分區的原則是使選取的數據最多 ) 。
第 8 步:根據前面用 hash_fun_2 函數計算好的 hash 值,建立 hash table 。
第 9 步:讀取表 B ,采用位圖向量進行位圖向量過濾。
第 10 步:對通過過濾的數據采用 hash_fun_1 函數將數據映射到相應的分區中去,并計算 hash_fun_2 的 hash 值。
第 11 步:如果所落的分區在內存中,則將前面通過 hash_fun_2 函數計算所得的 hash 值與內存中已存在的 hash table 做連接, 將結果寫致磁盤上。 如果所落的分區不在內存中,則將相應的值與表 S 相應的分區放在一起。
第 12 步:繼續讀取表 B ,重復第 9 步,直至表 B 讀取完畢。
第 13 步:讀取相應的 (Si,Bi) 做 hash 連接。在這里會發生動態角色互換。
第 14 步:如果分區過后,最小的分區也比內存大,則發生 nested- loop hash join 。
Hash Join 的成本
1. In-Memory Hash Join
Cost(HJ)=Read(S)+ build hash table in memory(CPU)+Read(B) + Perform In memory Join(CPU)
?? 忽略 cpu 的時間,則
? Cost(HJ)=Read(S)+Read(B)
| ? |
2. On-Disk Hash Join
根據上述的步驟描述,我們可以看出
Cost(HJ)=Cost(HJ1)+Cost(HJ2)
其中 Cost(HJ1) 的成本就是掃描 S,B 表,并將無法放在內存上的部分寫回磁盤,對應前面第 2 步至第 12 步。
Cost(HJ2) 即為做 nested-loop hash join 的成本,對應前面的第 13 步至第 14 步。
其中 Cost(HJ1) 近似等于 Read(S)+Read(B)+Write((S-M)+(B-B*M/S)) 。
因為在做 nested-loop hash join 時,對每一 chunk 的 build input ,都需要讀取整個 probe input ,因此
Cost(HJ2) 近似等于 Read((S-M)+n*(B-B*M/S))
其中 n 是 nested-loop hash join 需要循環的次數。 n=(S/F)/M
一般情況下,如果 n 大于 10 的話, hash join 的性能將大大下降。從 n 的計算公式可以看出, n 與 Fan-out 成反比例,
提高 fan-out ,可以降低 n 。當 hash_area_size 是固定時,可以降低 cluster size 來提高 fan-out 。
從這里我們可以看出,提高 hash_multiblock_io_count 參數的值并不一定提高 hash join 的性能。
當連接的兩個表是用等值連接并且表的數據量比較大時,優化器才可能采用哈希連接。哈希連接是基于CBO的。只有在數據庫
初始化參數HASH_JOIN_ENABLED設為True,并且為參數PGA_AGGREGATE_TARGET設置了一個足夠大的值的時候,Oracle才會使用哈
希邊連接。HASH_AREA_SIZE是向下兼容的參數,但在Oracle9i之前的版本中應當使用HASH_AREA_SIZE。當使用ORDERED提示時,
FROM子句中的第一張表將用于建立哈希表。
當缺少有用的索引時,哈希連接比嵌套循環連接更加有效。哈希連接也可能比嵌套循環連接更快,因為處理內存中的哈希表
比檢索B_樹索引更加迅速。 Hash Join 適合于小表與大表連接、返回大型結果集的連接。
深入地理解和掌握oracle的表連接對于優化數據庫的性能至關重要。由于優化器選擇方式的不同,以及統計信息的缺失或統
計信息的不準確,ORACLE自動選擇的表連接方式不一定是最優的。當SQL語句的執行效率很低時,可通過auto trace對執行計
劃進行跟蹤和分析。當出現多表連接時,需要仔細分析是否有更佳的連接條件。根據系統的特點,必要時可以在SQL中添加
HINTS,從而改變SQL的執行計劃,從而達到性能優化的目的。
6 索引連接
如果一組已存在的索引包含了查詢所需要的所有信息,那么優化器將在索引中有選擇地生成一組哈希表。可通過范圍或者快
速全局掃描訪問到每一個索引,而選擇何種掃描方式取決于WHERE子句中的可有條件。在一張表有大量的列,而您只想訪問有
限的列時,這種方法非常有效。WHERE子句約束條件越多,執行速度越快。因為優化器在評估執行查詢的優化路徑時,將把約
束條件作為選項看待。您必須在合適的列(那些滿足整個查詢的列)上建立索引,這樣可以確保優化器將索引連接作為可選
項之一。這個任務通常牽涉到在沒有索引,或者以前沒有建立聯合索引的列上增加索引。相對于快速全局掃描,連接索引的
優勢在于:快速全局掃描只有一個單一索引滿足整個查詢;索引連接可以有多個索引滿足整個查詢。
------end------
(url: http://www.linuxidc.com/Linux/2010-08/28282.htm)
與50位技術專家面對面20年技術見證,附贈技術全景圖
總結
以上是生活随笔為你收集整理的Oracle表连接深入浅出的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 出现ORA - 1017用户名/口令无效
- 下一篇: ORACLE RAC 重新安装时清空AS