oracle sql 高階程式設計學習筆記(二十七)
半聯結定義
當兩張表進行聯結的時候,如果表1中的資料行是否出現在結果集中需要根據表2中出現或不出現至少一個相匹配的資料行來判斷,這種情況就會發生半聯結;而反聯結便是半聯結的補集,它們會作為資料庫中常見的聯結方法如NESTED LOOPS,MERGE SORT JOIN,HASH JOIN的選項出現。 實際上半聯結和反聯結本身也可以被認同是兩種聯結方法;在CBO優化模式下,優化器能夠根據實際情況靈活的轉換執行語句從而實現半聯結和反聯結方法,畢竟沒有什麼SQL語法可以顯式的呼叫半聯結和反聯結,它們只是SQL語句滿足某些條件時優化器可以選擇的選項而已,不過仍然有必要深入這兩種選項在特定情況下帶來的效能優勢。
一、半聯結必要條件
半聯結是一種可以極大提升某些查詢效能的優化方法。基於成本優化器決定使用半聯結的必要條件 1、語句必須使用關鍵字in(=any)或exists 2、語句必須在in 或exists子句中有查詢 3、如果語句使用exists語法。則必須使用相關子查詢 4、in和exists子句不能包含在or分支中
二、半聯結執行計劃
實際上半聯結本身並不是一種聯結方法,而更像其他聯結方法的一個選項。oracle 最常用的三種聯結方法巢狀迴圈、合併聯結、雜湊聯結都可以應用到半聯結。同時還要記住允許處理過程在子查詢中找到第一條匹配記錄時停止是一種優化方法。來看如下偽碼: Q1外層查詢,Q2內層查詢
open Q1 while Q1 still has records fetch record from Q1 result=false open Q2 while Q2 still has records fetch record from Q2 if(Q1.record matchs Q2.record) then --半聯結優化 不用遍歷所有的內層記錄 result=true exit loop end if end loop close Q2 if(result=true) reture Q1 record end loop close Q1
半聯結提供子查詢中找到第一條匹配記錄時跳出內層迴圈(不用遍歷所有的內層記錄)的if語句。顯然,對於大資料集,與對外層查詢中的每一行資料都必須迴圈讀取內層查詢返回的所有記錄的普通巢狀迴圈聯結相比,這個技術可以節省大量時間。
1、in 執行計劃
select dept.department_id
from departments dept
where dept.department_id in (select emp.department_id from employees emp);
2、exists執行計劃
select dept.department_id
from departments dept
where exists(select 1 from employees emp where emp.department_id=dept.department_id);
可以看到in和exists 的執行計劃是一致的。統計資訊也是一致的。在8i以後in和exists處理的方法就是一致的了。 可以通過自動追蹤的統計資訊的追蹤檔案來證明in exists 都轉化成了相同的語句開啟自動追蹤命令:
alter session set events '10053 trace name context forever,level 1';
擴充套件 關閉跟蹤事件
ALTER SESSION SET EVENTS '10053 trace name context off';
檢視in exists 執行計劃後
查詢trace檔案路徑
SELECT value FROM v$diag_info WHERE name='Default Trace File';
搜尋Cost-Based Subquery Unnesting 找到如下段落 截圖中標紅框標註可以證明in其實就是any。他們作用是一致的。 這裡意思把any進行了子查詢解巢狀繼續
再來看exists的追蹤檔案
可以看到 in 和exists最終都進行了子查詢解巢狀,轉換為相同語句: SELECT “DEPT”.“DEPARTMENT_ID” “DEPARTMENT_ID” FROM “HR”.“EMPLOYEES” “EMP”,“HR”.“DEPARTMENTS” “DEPT” WHERE “EMP”.“DEPARTMENT_ID”=“DEPT”.“DEPARTMENT_ID” 這與以前學習的內容子查詢解巢狀結論是一致的。
三、控制半聯結執行計劃
1、提示控制半聯結執行計劃
從11gR2起就可以使用如下提示: semijoin–半聯結 no_semijoin–不使用半聯結 (優化器選擇使用哪種型別) nl_sj --迴圈巢狀半聯結(10g起棄用) hash_sj --雜湊半聯結 (10g起棄用) merge_sg --合併半聯結(10g起棄用)
–使用 no_semijoin 的exists語句
select dept.department_id
from departments dept
where exists(select /*+no_semijoin*/ 1 from employees emp where emp.department_id=dept.department_id);
可以看到no_semijoin 提示關閉了優化器使用半聯結的能力。查詢使用了filter 運算來將兩個行資料來源結合起來。 注意解釋執行計劃的謂語部分是用filter運算執行exitsts子句。解釋執行計劃是獨立於優化器的單獨的程式碼路徑, 它會與實際執行計劃不同。 來看實際執行計劃:
SELECT t.SQL_TEXT, t.SQL_ID, t.CHILD_NUMBER
FROM v$sql t
WHERE t.SQL_TEXT LIKE '%gather_plan_statistics%';
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('5cupcamb9ts5d',0,'ALLSTATS LAST'));
可以看到實際計劃中的謂語部分比解釋計劃要少得多
2、使用always_semi_join引數控制半聯結
隱藏引數對優化器選擇半聯結也可以進行控制。_always_semi_join 最開始是一個正常的引數,在9i開始變成隱藏引數。 _always_semi_join有效值
SELECT name_kspvld_values NAME, value_kspvld_values VALUE
FROM x$kspvld_values v
WHERE name_kspvld_values = '_always_semi_join';
這個引數的名字容易引起誤解,因為它並不是強制進行半聯結。預設值為choose,允許優化器,對所有的半聯結方法進行評估並選出它認為最高效的方法。off 則是禁用半聯結。將優化器引數設定為merge。
alter session set "always_semi_join"= MERGE ;
再來看執行計劃
select dept.department_name
from departments dept
where dept.department_id in (select emp.department_id from employees emp);
四、 半聯結限制條件
對於優化器選擇使用半聯結文件中,只說明瞭一個主要限制條件(11gR2中)優化器不會為任何使用包含在or分支 中的子查詢選擇半聯結。在之前的oracle版本中,包含distinct關鍵字時也會禁用半聯結,但現在也沒有這個限制了。 先設定_always_semi_join 引數為hash半聯結
alter session set "_always_semi_join"=hash;
再來檢視使用or子句時的執行計劃
select dept.department_name
from departments dept
where 1=2 or dept.department_id in (select emp.department_id from employees emp);
很明顯計劃中的禁用了半聯結,採用了filter演算法。