鏈接謂詞推入(Join Predicate Pushdown)是優化器處理帶視圖的目標SQL的一種優化手段,它是指雖然優化器會把該SQL中視圖的定義SQL語句看成一個獨立單元來單獨執行,但此時優化器會把本來處於該視圖外部查詢中和該視圖之間的鏈接條件推入到該視圖的定義SQL語句內部,這樣是爲了能使用上該視圖內部相關基表上的索引,進而能走出基於索引的嵌套循環鏈接。sql
鏈接謂詞推入所帶來的基於索引的嵌套循環鏈接並不必定能走出更高效的執行計劃,由於當作了鏈接謂詞推入後,原目標SQL中的視圖就和外部查詢產生了關聯,同時Oracle又必須將該視圖的定義SQL語句看成一個獨立的處理單元單獨執行,這也就意味着對於外部查詢所在結果集中的每一條記錄,上述視圖的定義SQL語句都得單獨執行一次,這樣一旦外部查詢所在的結果集的Cardinality比較大的話,即使在執行上述視圖的定義語句時能用上索引,整個SQL的執行效率也不定比不作鏈接謂詞推入時的哈希鏈接或排序合併鏈接高。因此Oracle在作鏈接謂詞推入時會考慮成本,只有當通過鏈接謂詞推入後走嵌套循環鏈接的等價改寫SQL的成本值小於原SQL的成本值時,Oracle纔會對目標SQL作鏈接謂詞推入。oracle
Oracle是否能作鏈接謂詞推入與目標視圖的類型、該視圖與外部查詢之間的鏈接類型以及鏈接方法有關。到目前爲止,Oracle僅僅支持對以下類型的視圖作鏈接謂詞推入。ide
視圖定義SQL語句中包含UNION ALL/UNION的視圖測試
視圖定義SQL語句中包含DISTINCT的視圖優化
視圖定義SQL語句中包含GROUP BY的視圖server
和外部查詢之間的鏈接類型是外鏈接的視圖htm
和外部查詢之間的鏈接類型是反鏈接的視圖blog
和外部查詢之間的鏈接類型是半鏈接的視圖排序
看一個鏈接謂詞推入的實例,建立測試表、相關索引和一個普通視圖和一個帶有UNION ALL的視圖索引
scott@TEST>create table emp1 as select * from emp; Table created. scott@TEST>create table emp2 as select * from emp; Table created. scott@TEST>create index idx_emp1 on emp1(empno); Index created. scott@TEST>create index idx_emp2 on emp2(empno); Index created. scott@TEST>create or replace view emp_view as 2 select emp1.empno as empno1 from emp1; View created. scott@TEST>create or replace view emp_view_union as 2 select emp1.empno as empno1 from emp1 3 union all 4 select emp2.empno as empno1 from emp2; View created.
執行測試SQL
scott@TEST>select /*+ no_merge(emp_view) */ emp.empno 2 from emp,emp_view 3 where emp.empno=emp_view.empno1(+) 4 and emp.ename='FORD'; EMPNO ---------- 7902
在上面的SQL中,咱們使用了no_merge hint是爲了讓Oracle不對視圖EMP_VIEW作視圖合併,這樣就具有了作鏈接謂詞推入的基本條件。這裏外部查詢和視圖EMP_VIEW的鏈接條件爲「emp.empno=emp_view.empno1(+)」,因爲已經在視圖EMP_VIEW的基表EMP1的列EMPNO上建立了索引IDX_EMP1,並且這裏的鏈接類型又是外鏈接,根據前面的介紹,對於視圖EMP_VIEW而言,全部能作鏈接謂詞推入的條件都已具有,Oracle在執行上面的SQL時會考慮作鏈接謂詞推入。若是作鏈接謂詞推入,執行計劃就會 走嵌套循環外鏈接而且訪問視圖EMP_VIEW的基表EMP1時會使用列EMPNO上的索引IDX_EMP1。
從執行計劃上能夠看出,Oracle在執行測試SQL時確實走的是嵌套循環外鏈接,而且訪問視圖EMP_VIEW的基表EMP1時用到了索引IDX_EMP1。並且Id=3的執行步驟上Name列的值是「EMP_VIEW」,Operation列的值是「VIEW PUSHED PREDICATE」。這說明Oracle確實沒有對視圖EMP_VIEW作視圖合併,而是把它看成一個獨立的執行單元來單獨執行,而且把外部查詢和視圖EMP_VIEW之間的鏈接條件「emp.empno=emp_view.empno1(+)」推入到了視圖的定義語句內部。
若是不作鏈接謂詞推入,那Oracle在訪問視圖EMP_VIEW的基表EMP1時就只能作全表掃描了。在測試SQL中加入no_push_pred hint(讓優化器不要對視圖EMP_VIEW作鏈接謂詞推入)再次執行
scott@TEST>select /*+ no_merge(emp_view) no_push_pred(emp_view) */ emp.empno 2 from emp,emp_view 3 where emp.empno=emp_view.empno1(+) 4 and emp.ename='FORD'; EMPNO ---------- 7902
執行計劃已經變爲了HASH JOIN OUTER,並且對EMP_VIEW的基表EMP1確實用的是全表掃描。
如今把測試SQL改一下,把EMP_VIEW用EMP_VIEW_UNION視圖替換,並把鏈接類型改成內鏈接,再次執行
scott@TEST>select emp.empno 2 from emp,emp_view_union 3 where emp.empno=emp_view_union.empno1 4 and emp.ename='FORD'; EMPNO ---------- 7902 7902
視圖EMP_VIEW_UNION的定義SQL語句中包含UNION ALL,它自己就不能作視圖合併,於是具有了作鏈接謂詞推入的基本條件。這裏外部查詢和視圖EMP_VIEW_UNION的鏈接條件爲「emp.empno=emp_view_union.empno1」視圖對基表上的EMPNO列都有索引,雖然這裏的鏈接類型是內鏈接,但對於包含UNION ALL的視圖EMP_VIEW_UNION而言,全部能做鏈接謂詞推入的條件都已具有,意味着Oracle地執行上述SQL時作考慮作鏈接謂詞推入。若是作鏈接謂詞推入,那執行計劃就會走嵌套循環鏈接,而且訪問視圖的基表會用上列EMPNO上的索引。
從執行計劃中能夠看出,Oracle走的執行計劃與預想的同樣。
在SQL中加入no_push_pred hint(讓優化器不要對視圖EMP_VIEW作鏈接謂詞推入)再次執行
scott@TEST>select /*+ no_push_pred(emp_view_union) */emp.empno 2 from emp,emp_view_union 3 where emp.empno=emp_view_union.empno1 4 and emp.ename='FORD'; EMPNO ---------- 7902 7902
從執行計劃能夠看出,不使用鏈接謂詞推入,則對視圖的基表作的是全表掃描。
以前提到過,Oracle在作鏈接謂詞推入時會考慮成本,只有通過鏈接謂詞推入後走嵌套循環鏈接的等價改寫SQL的成本值小於原SQL的成本值時,Oracle纔會對目標SQL作鏈接謂詞推入。
如今來驗證一下,在上面的SQL中加入cardinality hint,讓CBO認爲外圍查詢的結果集的Cardinality是1萬,這樣就會急劇增長作鏈接謂詞推入後的嵌套循環鏈接的成本,若是Oracle在作鏈接謂詞推入是確實會考慮成本,那麼此時Oracle就必定不會再選擇作鏈接謂詞推入。
scott@TEST>select /*+ cardinality(emp 10000) */emp.empno 2 from emp,emp_view_union 3 where emp.empno=emp_view_union.empno1 4 and emp.ename='FORD'; EMPNO ---------- 7902 7902
scott@TEST>select /*+ cardinality(emp 10000) push_pred(emp_view_union) */emp.empno 2 from emp,emp_view_union 3 where emp.empno=emp_view_union.empno1 4 and emp.ename='FORD'; EMPNO ---------- 7902 7902
從上面的測試能夠看出使用cardinality hint後Oracle沒有選擇作鏈接謂詞推入,此時的成本爲10,使用push_pred強制作鏈接謂詞推入,看到成本爲20008。這也驗證了以前說的Oracle在作鏈接謂詞推入會考慮成本。
下面再看使用了內嵌視圖且鏈接類型爲外鏈接的示例:
scott@TEST>select /*+ no_merge(emp_view_inline) */ emp.empno 2 from emp,(select emp1.empno as empno1 from emp1) emp_view_inline 3 where emp.empno=emp_view_inline.empno1(+) 4 and emp.ename='FORD'; EMPNO ---------- 7902
對於上面的SQL,全部能作鏈接謂詞推入的條件都已具有,從執行計劃中也能夠看出Oracle確實也作了鏈接謂詞推入。
再回到一開始執行的SQL,把外鏈接改成內鏈接,並在其中加入push_pred hint(讓優化器對視圖EMP_VIEW作鏈接謂詞推入)和USE_NL hint
scott@TEST>select /*+ no_merge(emp_view) use_nl(emp_view) push_pred(emp_view) */ emp.empno 2 from emp,emp_view 3 where emp.empno=emp_view.empno1 4 and emp.ename='FORD'; EMPNO ---------- 7902
從執行計劃來看,Oracle沒有作鏈接謂詞推入,由於它不屬於開關提到的那幾種能作鏈接謂詞推入的情形,即便使用了Hint也不行。
雖然Oracle是否能作鏈接謂詞推入與目標視圖是否能作視圖合併、是不是內嵌視圖沒有關係,可是與目標視圖的類型、與外查詢之間的鏈接類型及鏈接方法是有關係的。到目前爲止,Oracle裏能作鏈接謂詞推入的情形公限於開頭提到的那幾種類型,若是不屬於這些情形,即使是看起來很簡單,Oracle也不會作。
參考《基於Oracle的SQL優化》
官方文檔:http://docs.oracle.com/cd/E11882_01/server.112/e41573/optimops.htm#i55050