大神級回答exists與in的區別

findumars發表於2013-12-25

google搜了一下,很多帖子,而且出發點不同,各有各的道理,但是有一個帖子講的特別好:

http://zhidao.baidu.com/question/134174568.html

忍不住在百度上回復了一下,怒贊,沒想到別人早就回復過了:圍觀大神級回答。確實名副其實!

================in和exists=============================
in 是把外表和內表作hash 連線,而exists是對外表作loop迴圈,每次loop迴圈再對內表進行查詢。

如果兩個表中一個較小,一個是大表,則子查詢表大的用exists,子查詢表小的用in:
例如:表A(小表),表B(大表)
1:select * from A where cc in (select cc from B) 效率低,用到了A表上cc列的索引;
select * from A where exists(select cc from B where cc=A.cc) 效率高,用到了B表上cc列的索引。
相反的2:
select * from B where cc in (select cc from A) 效率高,用到了B表上cc列的索引;
select * from B where exists(select cc from A where cc=B.cc) 效率低,用到了A表上cc列的索引。

not in 和not exists如果查詢語句使用了not in 那麼內外表都進行全表掃描,沒有用到索引;而not extsts 的子查詢依然能用到表上的索引。所以無論那個表大,用not exists都比not in要快。

------------------------我的評論-----------------------------------------

1本質上是要利用索引,2要區分大表小表,3not exists子查詢能使用索引,速度更快。我的真實SQL是這樣的:

INSERT INTO journal_curr
SELECT '2008-10-01' AS date_imp, act_numero, act_sexe, titre, act_nom, act_nom_abrege, act_sous_titre,act_adr_numero,act_adr_rue,
ltrim(concat( IFNULL(act_adr_numero,''),' ',act_adr_rue)) AS act_adr,act_adr_complement,act_cp,LEFT(act_cp,2) AS region,act_ville,act_pays, nb_exempl
FROM journal_abonn INNER JOIN actif
ON (journal_abonn.ref_client = actif.act_numero)
AND (ref_journal='NOUVEURO')
AND (act_pays<>'ALLEMAGNE')
AND ( ((date_abonn<='2008-10-01') AND (date_fin_abonn>='2008-10-01') AND (date_abonn_fin_envoi IS NULL))
OR ((date_abonn<='2008-10-01') AND (date_abonn_fin_envoi>='2008-10-01'))
OR ((act_type_abonne=4) AND ((date_fin_abonn>='2008-10-01') AND (date_abonn_fin_envoi IS NULL)))
OR ((act_type_abonne=4) AND (date_fin_abonn IS NULL) AND (date_abonn_fin_envoi IS NULL))
OR ((act_type_abonne=4) AND ((date_abonn_fin_envoi>='2008-10-01')))
)
AND (ref_abonn_new NOT IN (SELECT ref_abonn_new FROM journal_suspension
WHERE (debut_suspension<='2008-10-01')
AND (fin_suspension>='2008-10-01')
AND (ref_journal='NOUVEURO')
)
)

自從給journal_abonn加了ref_client加了索引以後,時間從30分鐘下降到幾秒(注意,我沒給ref_abonn_new加索引)。
有趣的是,我這個例子剛好journal_abonn是大表,journal_suspension是小表,符合IN的使用條件。
大表真正使用的關鍵索引是ref_client,而不是ref_abonn_new,所以可以充分利用上。
小表計算速度無所謂,而且根據研究小表應該也是一次計算完後放在記憶體中。
另外,我這裡是NOT IN,但是速度仍然很快,說明仍然利用上了索引,難道大神級回覆也有小錯?

================================================================

 

另外,我重新整理一下所有我搜到的發言,都很有道理:

通過使用exist,oracle系統會首先檢查主查詢,然後執行子查詢直到它找到第一個匹配項,這就節省了時間(我的評論,主表小當然應該先過濾主查詢)。oracle系統在執行in子查詢時,首先執行子查詢(我的評論,從表小當然應該先過濾子查詢),並將獲得的結果列表存放在在一個加了索引的臨時表中。在執行子查詢之前,系統先將主查詢掛起,待子查詢執行完畢,存放在臨時表中以後再執行主查詢。這也就是使用exists比使用in通常查詢速度快的原因。

 

下面是一個非關聯子查詢: 
  select staff_name from staff_member where staff_id 
  in (select staff_id from staff_func); 
  而下面是一個關聯子查詢: 
  select staff_name from staff_member where staff_id in (select staff_id from staff_func where staff_member.staff_id=staff_func.staff_id); 
  以上返回的結果集是相同的,可是它們的執行開銷是不同的: 
  非關聯查詢的開銷——非關聯查詢時子查詢只會執行一次,而且結果是排序好的,並儲存在一個ORACLE的臨時段中,其中的每一個記錄在返回時都會被父查詢所引用。在子查詢返回大量的記錄的情況下,將這些結果集排序,以及將臨時資料段進行排序會增加大量的系統開銷。 
  關聯查詢的開銷——對返回到父查詢的的記錄來說,子查詢會每行執行一次。因此,我們必須保證任何可能的時候子查詢用到索引。 

 

樓上說法片面,in和exist,各有快的時候,主要是看你的篩選條件是在主查詢上還是在子查詢上
下面是oracle文件,:)

In certain circumstances, it is better to use IN rather than EXISTS. In general, if the selective predicate is in the subquery, then use IN. If the selective predicate is in the parent query, then use EXISTS.

Sometimes, Oracle can rewrite a subquery when used with an IN clause to take advantage of selectivity specified in the subquery. This is most beneficial when the most selective filter appears in the subquery and there are indexes on the join columns. Conversely, using EXISTS is beneficial when the most selective filter is in the parent query. This allows the selective predicates in the parent query to be applied before filtering the rows against the EXISTS criteria.

 

exists不需要記錄,當存在的時候就返回。
用exists只檢查行的存在性,而in檢查到行裡的實際的值。

 

參考:
http://bbs.csdn.net/topics/190124638
http://blog.csdn.net/lick4050312/article/details/4476333

 

A,B兩個表
當只顯示一個表的資料如A,關係條件只一個如ID時,使用IN更快:select * from A where id in (select id from B)
當只顯示一個表的資料如A,關係條件不只一個如ID,col1時,使用IN就不方便了,可以使用EXISTS:select * from A where exists (select 1 from B where id = A.id and col1 = A.col1)
當只顯示兩個表的資料時,使用IN,EXISTS都不合適,要使用連線:select * from A left join B on id = A.id

參考:

http://www.cnblogs.com/AllUserBegin/p/3513084.html

相關文章