PLSQL高效設計之exists和in
<![endif]--> <![endif]-->
發現公司同事很喜歡用exists 和in 做子查詢關聯,我覺得很有必要研究下
兩者的區別,供參考和備忘
/* (這段資訊來自網路begin )對於in 的使用,就相當於對inner table 執行一個帶distinct 的子查詢,然後將得到的結果集再和outer table 進行外連線,連線方式和索引使用任然同於普通兩表的連線(這段資訊來自網路end )*/
對於網路的這段描述,我給予了測試,測試表為 liomuser.staff ,和liomuser.department ,這兩張表都是小表,數量在1 萬左右。
-- 例如:
select *
from liomuser.staff
where department_id in ( select department_id from liomuser.department);
-- 可以轉換為
select a.*
from liomuser.staff a,
( select distinct department_id from liomuser.department) b
where a.department_id = b.department_id;
執行計劃分別如下:
( 1 ) select *
from liomuser.staff
where department_id in ( select department_id from liomuser.department);
( 2 ) select a.*
from liomuser.staff a,
( select distinct department_id from liomuser.department) b
where a.department_id = b.department_id;
我選擇的是兩個小表,從資料上看採用外連線的方式除了一致性讀要稍微小一點,兩者執行計劃和統計資訊幾乎一樣。
測試結果顯示對於小表網路上給出的描述是正確的
但是以我的經驗,in 的方式應該比外連線效能要差很多,按照上面的測試,兩者似乎是一樣的執行路徑,是不是應為表資料量少的緣故呢?
我決定採用兩張大表做測試,cust_order 和order_detail 這兩張表的資料量都在一千萬以上。
首先測試in ,語句如下:
select a.*
from liomuser.cust_order a
where order_id in ( select order_id from liomuser.order_detail b);
執行計劃如下:
測試2 外連線,語句如下:
select a.*
from liomuser.cust_order a,
( select distinct order_id from liomuser.order_detail) b
where a.order_id = .order_id ;
執行計劃如下:
對著兩個大表的in 和外連線的對比可以看出,採用外連線的執行計劃明顯優於in 的方式,採用in 方式則表連線採用nested loop 方式,而外連線採用了HASH JOIN ,
並且in 方式的CPUcost 要比外連線大1/3, 這樣看來,對於小表,或者說inner table 是小表的查詢,in 和外連線都差不多,但是對於大表,特別是inner table 資料量巨大的時候,採用外連線要好很多。
由此看出,in 並不完全等同於與inner table 做distinct 外連線,但是外連線要比in 有效率得多。
下面討論下 EXIST
實際上exists 等同於先對outer table 進行掃描,從中檢索到每行和inner table 做迴圈匹配,執行計劃如下:
註釋:部分網上資料認為exists 對outer table 會進行全表掃描,但是在該執行計劃中沒有發現全表掃描,仍然走了索引。
Exists 的寫法可以轉換成:
declare cnt number ( 10 );
for cur in ( select a.*from liomuser.cust_order a) loop
cnt:= 0 ;
select count ( 1 ) into cnt from liomuser.order_detail where order_id=cur.order_id;
if cnt<> 0 then
return cur;
end if ;
end loop ;
exists 與 in 的比對:
語句 1 , in
語句 2 , exsits
從執行計劃上來看沒有任何區別,再讓我們看看執行的統計資訊:
語句 1 , in
select a.*
from liomuser.cust_order a
where order_id in ( select order_id from liomuser.order_detail b)
語句 2 , exists
select a.*
from liomuser.cust_order a
where exists
( select 1 from liomuser.order_detail b where a.order_id = b.order_id)
從兩種方式統計資訊可以看出,採用 exists 的一致性讀要比 in 要好,但是 bytessent 要比 in 高,這個也從側面驗證了前面所說的 exists 相當於 loop
通過上面解釋現在很容易明白當 inner table 資料量巨大且索引情況不好 ( 大量重複值等 ) 則不宜使用產生對 inner table 檢索而導致系統開支巨大 IN 操作,建議對 innertable 過大的查詢,採取 exsits ,或者外連線方式
另外: NOT IN 子句將執行個內部排序和合並 . 無論在哪種情況下 ,NOT IN 都是
最低效 ( 它對子查詢中表執行了個全表遍歷 ). 為了避免使用 NOT IN , 我們可以把它改寫成外連線 (Outer Joins) 或 NOT EXISTS