oracle 表連接方式解析

一、表的連接

  表的連接是指在一個SQL語句中通過表與表之間的關聯,從一個或多個表檢索出相關的數據。連接是通過SQL語句中FROM從句的多個表名,以及WHERE從句裏定義的表之間的連接條件來實現的。如果一個SQL語句的關聯表超過兩個,那麼連接的順序如何呢?ORACLE首先連接其中的兩個表,產生一個結果集;然後將產生的結果集與下一個表再進行關聯;繼續這個過程,直到所有的表都連接完成;最後產生所需的數據。下面都以兩個表的連接爲例:

  createtableuser_info(user_namechar(10),user_idchar(10));

  createtabledev_info(dev_nochar(10),user_idchar(10),dev_typechar(10));

  說明和分析表的各種連接方式。

  ORACLE6的版本開始,優化器使用4種不同的表的連接方式:

  • 嵌套循環連接(NESTEDLOOPJOIN

  • 羣集連接CLUSTERJOIN

  • 排序合併連接(SORTMERGEJOIN

  • 笛卡爾連接(CARTESIANJOIN

  • ORACLE7.3中,新增加了哈希連接(HASHJOIN

  • ORACLE8中,新增加了索引連接(INDEXJOIN

  這六種連接方式都有其獨特的技術特點,在一定的條件下,可以充分發揮高效的性能。

  但是也都有其侷限性,如果使用不當,不僅不能提高效率,反而會嚴重影響系統的性能。因此,深入地探討連接方式的內部運行機制對於性能優化是必要的。

  1、嵌套循環連接

  嵌套循環連接的內部處理的流程:

  1Oracle優化器根據基於規則RBOrulebasedoptimizer)或基於成本CBOcostbasedoptimizer)的原則,選擇兩個表中的一個作爲驅動表,並指定其爲外部表。

  2Oracle優化器再將另外一個表指定爲內部表。

  3Oracle從外部表中讀取第一行,然後和內部表中的數據逐一進行對比,所有匹配的記錄放在結果集中。

  4Oracle讀取外部表中的第二行,再和內部表中的數據逐一進行對比,所有匹配的記錄添加到結果集中。

  5重複上述步驟,直到外部表中的所有紀錄全部處理完。

  6最後產生滿足要求的結果集。

  通過查詢SQL語句的執行計劃可以看出哪個表是外部表,哪個爲內部表。

  如selecta.user_name,b.dev_nofromuser_infoa,dev_infobwherea.user_id=b.user_id;

  上面的表是外部表,即驅動表。下面的表是內部表的執行計劃:

  SELECTSTATEMENTOptimizer=CHOOSE
  NESTEDLOOPS
  TABLEACCESS(FULL)OF'USER_INFO'
  TABLEACCESS(FULL)OF'DEV_INFO'

  使用嵌套循環連接是一種從結果集中提取第一批記錄最快速的方法。在驅動行源表(就是正在查找的記錄)較小、或者內部行源表已連接的列有惟一的索引或高度可選的非惟一索引時,嵌套循環連接效果是比較理想的。嵌套循環連接比其他連接方法有優勢,它可以快速地從結果集中提取第一批記錄,而不用等待整個結果集完全確定下來。這樣,在理想情況下,終端用戶就可以通過查詢屏幕查看第一批記錄,而在同時讀取其他記錄。不管如何定義連接的條件或者模式,任何兩行記錄源可以使用嵌套循環連接,所以嵌套循環連接是非常靈活的。

  然而,如果內部行源表(讀取的第二張表)已連接的列上不包含索引,或者索引不是高度可選時,嵌套循環連接效率是很低的。如果驅動表的記錄非常龐大時,其他的連接方法可能更加有效。

  可以通過在SQL語句中添加HINTS,強制ORACLE優化器產生嵌套循環連接的執行計劃。

  select/*+use_nl(ab)*/a.user_name,b.dev_nofromuser_infoa,dev_infobwherea.user_id=b.user_id;

2、羣集連接(CLUSTERJOIN

  羣集連接實際上是嵌套循環連接的一種特例。如果所連接的兩張源表是羣集中的表,即兩張表屬於同一個段(SEGMENT),那麼ORACLE能夠使用羣集連接。處理的過程是:ORACLE從第一張行源表中讀取第一行,然後在第二張行源表中使用CLUSTER索引查找能夠匹配到的紀錄;繼續上面的步驟處理行源表中的第二行,直到所有的記錄全部處理完。

  羣集連接的效率極高,因爲兩個參加連接的行源表實際上處於同一個物理塊上。但是,羣集連接也有其限制,沒有羣集的兩個表不可能用羣集連接。所以,羣集連接實際上很少使用。

3、排序合併連接(SORTMERGEJOIN

  排序合併連接內部處理的流程:

  1)優化器判斷第一個源表是否已經排序,如果已經排序,則到第3步,否則到第2步。

  2)第一個源表排序

  3)優化器判斷第二個源表是否已經排序,如果已經排序,則到第5步,否則到第4步。

  4)第二個源表排序

  5)已經排過序的兩個源表進行合併操作,並生成最終的結果集。

  在缺乏數據的選擇性或者可用的索引時,或者兩個源表都過於龐大(所選的數據超過表記錄數的5%)時,排序合併連接將比嵌套循環連更加高效。

  排列合併連接需要比較大的臨時內存塊,以用於排序,這將導致在臨時表空間佔用更多的內存和磁盤I/O

  selecta.user_name,b.dev_nofromuser_infoa,dev_infobwherea.user_id>b.user_id;
  Plan
  --------------------------------------------------
  SELECTSTATEMENTOptimizer=CHOOSE(Cost=7Card=336Bytes=16128)
  MERGEJOIN(Cost=7Card=336Bytes=16128)
  SORT(JOIN)(Cost=4Card=82Bytes=1968)
  TABLEACCESS(FULL)OF'USER_INFO'(Cost=2Card=82Bytes=1968)
  SORT(JOIN)(Cost=4Card=82Bytes=1968)
  TABLEACCESS(FULL)OF'DEV_INFO'(Cost=2Card=82Bytes=1968)

  可以通過在SQL語句中添加HINTS,強制ORACLE優化器產生排序合併連接的執行計劃。

  select/*+use_merge(ab)*/a.user_name,b.dev_nofromuser_infoa,dev_infobwherea.user_id>b.user_id;

  4、笛卡爾連接(CARTESIANJOIN

  笛卡爾連接是指在sql語句中沒有寫出表連接的條件,優化器把第一個表的每一條記錄和第二個表的所有紀錄相連接。如果第一個表的紀錄數爲m,第二個表的紀錄數爲m,則會產生m*n條紀錄數。

  下面的查詢,未指名連接條件,就會產生笛卡爾連接。

  selecta.user_name,b.dev_nofromuser_infoa,dev_infob;

  由於笛卡爾連接會導致性能很差的SQL,因此一般也很少用到。

5、哈希連接

  當內存能夠提供足夠的空間時,哈希(HASH)連接是Oracle優化器通常的選擇。哈希連接中,優化器根據統計信息,首先選擇兩個表中的小表,在內存中建立這張表的基於連接鍵的哈希表;優化器再掃描表連接中的大表,將大表中的數據與哈希表進行比較,如果有相關聯的數據,則將數據添加到結果集中。

  當表連接中的小表能夠完全cache到可用內存的時候,哈希連接的效果最佳。哈希連接的成本只是兩個表從硬盤讀入到內存的成本

  但是,如果哈希表過大而不能全部cache到可用內存時,優化器將會把哈希表分成多個分區,再將分區逐一cache到內存中。當表的分區超過了可用內存時,分區的部分數據就會臨時地寫到磁盤上的臨時表空間上。因此,分區的數據寫磁盤時,比較大的區間(EXTENT)會提高I/O性能。ORACLE推薦的臨時表空間的區間是1MB。臨時表空間的區間大小由UNIFORMSIZE指定。

  當哈希表構建完成後,進行下面的處理:

  1)第二個大表進行掃描

  2如果大表不能完全cache到可用內存的時候,大表同樣會分成很多分區

  3)大表的第一個分區cache到內存

  4對大表第一個分區的數據進行掃描,並與哈希表進行比較,如果有匹配的紀錄,添加到結果集裏面5與第一個分區一樣,其它的分區也類似處理。

  6所有的分區處理完後,ORACLE對產生的結果集進行歸併,彙總,產生最終的結果。

  當哈希表過大或可用內存有限,哈希表不能完全CACHE到內存。隨着滿足連接條件的結果集的增加,可用內存會隨之下降,這時已經CACHE到內存的數據可能會重新寫回到硬盤去。如果出現這種情況,系統的性能就會下降。

  當連接的兩個表是用等值連接並且表的數據量比較大時,優化器纔可能採用哈希連接。哈希連接是基於CBO的。只有在數據庫初始化參數HASH_JOIN_ENABLED設爲True,並且爲參數PGA_AGGREGATE_TARGET設置了一個足夠大的值的時候,Oracle纔會使用哈希連接。HASH_AREA_SIZE是向下兼容的參數,但在Oracle9i之前的版本中應當使用HASH_AREA_SIZE。當使用ORDERED提示時,FROM子句中的第一張表將用於建立哈希表。

  selecta.user_name,b.dev_nofromuser_infoa,dev_infobwherea.user_id=b.user_id;
  Plan
  ----------------------------------------------------------
  0SELECTSTATEMENTOptimizer=CHOOSE(Cost=5Card=82Bytes=3936)
  10HASHJOIN(Cost=5Card=82Bytes=3936)
  21TABLEACCESS(FULL)OF'USER_INFO'(Cost=2Card=82Bytes=1968)
  31TABLEACCESS(FULL)OF'DEV_INFO'(Cost=2Card=82Bytes=1968)

  可以通過在SQL語句中添加HINTS,強制ORACLE優化器產生哈希連接的執行計劃。

  select/*+use_hash(ab)*/a.user_name,b.dev_nofromuser_infoa,dev_infobwherea.user_id=b.user_id;

  當缺少有用的索引時,哈希連接比嵌套循環連接更加有效。哈希連接也可能比嵌套循環連接更快,因爲處理內存中的哈希表比檢索B_樹索引更加迅速。

  6、索引連接

  如果一組已存在的索引包含了查詢所需要的所有信息,那麼優化器將在索引中有選擇地生成一組哈希表。可通過範圍或者快速全局掃描訪問到每一個索引,而選擇何種掃描方式取決於WHERE子句中的可有條件。在一張表有大量的列,而您只想訪問有限的列時,這種方法非常有效。WHERE子句約束條件越多,執行速度越快。因爲優化器在評估執行查詢的優化路徑時,將把約束條件作爲選項看待。您必須在合適的列(那些滿足整個查詢的列)上建立索引,這樣可以確保優化器將索引連接作爲可選項之一。這個任務通常牽涉到在沒有索引,或者以前沒有建立聯合索引的列上增加索引。相對於快速全局掃描,連接索引的優勢在於:快速全局掃描只有一個單一索引滿足整個查詢;索引連接可以有多個索引滿足整個查詢。

  假設表dev_info上有兩個索(一個在dev_no,一個在dev_type上)。

  作如下的查詢

  selectdev_no,dev_type
  fromuser_info
  whereuser_id=‘U101010’
  anddev_type=‘1010’;


三、幾種主要表連接的比較

類別

嵌套循環連接

排序合併連接

哈希連接

優化器提示

USE_NL

USE_MERGE

USE_HASH

使用的條件

任何連接

主要用於不等價連接,如<<=>>=;

但是不包括<>

僅用於等價連接

相關資源

CPU、磁盤I/O

內存、臨時空間

內存、臨時空間

特點

當有高選擇性索引或進行限制性搜索時效率比較高,能夠快速返回第一次的搜索結果。

當缺乏索引或者索引條件模糊時,排序合併連接比嵌套循環有效。

當缺乏索引或者索引條件模糊時,哈希連接連接比嵌套循環有效。通常比排序合併連接快。

在數據倉庫環境下,如果表的紀錄數多,效率高。

缺點

當索引丟失或者查詢條件限制不夠時,效率很低;當表的紀錄數多時,效率低。

所有的表都需要排序。它爲最優化的吞吐量而設計,並且在結果沒有全部找到前不返回數據。

爲建立哈希表,需要大量內存。第一次的結果返回較慢。

  四、結束語

  深入地理解和掌握oracle的表連接對於優化數據庫的性能至關重要。由於優化器選擇方式的不同,以及統計信息的缺失或統計信息的不準確,ORACLE自動選擇的表連接方式不一定是最優的。當SQL語句的執行效率很低時,可通過autotrace對執行計劃進行跟蹤和分析。當出現多表連接時,需要仔細分析是否有更佳的連接條件。根據系統的特點,必要時可以在SQL中添加HINTS,從而改變SQL的執行計劃,從而達到性能優化的目的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章