小表驅動大表, 兼論exists和in


給出兩個表,A和B,A和B表的數據量,

當A小於B時,用exists
select * from A where  exists (select * from B where A.id=B.id)
exists的實現,相當於外表循環,每次循環對內表進行查詢?
for i in A
    for j in B
        if j.id == i.id then ....
相反,如果A大於B的時候,則用in
select * from A where id in (select id from B)
這種在邏輯上類似於
for i in B
    for j in A
        if j.id == i.id then ....

 

然后MySQL實現in,使用了hash join (哈希連接)。我猜想和邏輯上的兩層循環在工作原理上是不一樣的。
 
經過我的實際測試
SELECT * FROM user u where exists  (SELECT 0 FROM plan p where p.user_id=u.user_id )
0.188 sec / 6.234 sec
 
SELECT * FROM user u where  user_id in  (SELECT user_id FROM plan)
0.015 sec / 0.000 sec
使用in的可謂是瞬間的
 
其中Plan表有3300行記錄,User表有3800,數據量恰好接近。
所以那個偽碼(兩層循環只能幫助理解),和實際情況差距很大。否則二者應該非常接近才對。
 
  Duration Fetch Rows  SQL
exists 0.17 6.2 924 SELECT * FROM user u where exists  (SELECT 0 FROM plan p where p.user_id=u.user_id )
in 0.01 0.0 924 SELECT * FROM user u where  user_id in  (SELECT user_id FROM plan)
 雖然數據量很小,但是可以想象,如果我們用exists,可能會非常糟糕,難怪都建議用in。
 
 
Hash Join
1. 什么事Hash Join。據說是Oracle7.3以后出現的一種連接技術,估計就是要解決本質形如兩層循環的工作方式吧(猜測)
那么,select * from A where id in (select id from B),我猜測實際處理中,會變為。
dict = hash(B)
for i in A
    if i in dict then ...

如果如此,當然會快很多了。並且之前提到過,B表小的時候,用in,在這里也正好被放到一個Hash表里面。

更多引用:
1、hash join 就是哈希連接,當一個表或多個表上沒有索引時,或者數據庫服務器必須從所有連接表讀取大量行的時候,就用這種方法。
2、orcale7.3以后才出來一種hash join的新的連接技術,hash join只能用於相等連接。
3、相對於以前的nested loop join連接技術,hash join更適合處理大型結果集,而且不需要在驅動表上建索引。
4、hash算法就是在兩個表連接的時候,首先要區分大表和小表,小表用S來表示,大表用B來表示,然后就把小表S表放在一個內存的hash table中。
5、若這個小表S表還是太大,放不進去hash table中,就應該對這個hash table要分區,對於大表B表也是要放在內存中的,若B表也是太大,那也要進行分區處理。
6、這樣的話,S表就分開若干個區,B表也分成若干個區,這樣就開始各個分區之間的互連。
7、除了hash table的分區后,還有一個就是hash area內存的感念,就是逐個處理S與B的各個子集相連情況,首先把小表放在內存里,內存是高速緩沖區,速度快,大表放在慢速的存儲區內,從慢速區內讀條數據后,再與高速區的做循環,這樣肯定會很快的,反過來就慢了,所以優化始終強調小表套大表,小表在高速緩沖區內執行,大表在慢速區放着。
8、S與B的兩兩相連時,會出現腳色互換的的,當B表的某個分區少的話,那這個小B表進如hash area中做主表。
9、hash join的核心就是確認小表為驅動表,在算法執行之前大小表都要拆分,會有兩者角色互換的情況,關鍵是看誰的數量小,小的進高速內存跑循環,大的在外面有IO處理過程。
10、總之hash join適合於小表與大表相連,返回大型結果集的連接。

從這里摘的:http://blog.sina.com.cn/s/blog_648760e30101b1uh.html

 
Not in
1. 不要使用Not in,解決辦法無外乎加索引,這樣會快一些,然后效果未必明顯,一般是改用Left join。
select * from A where id in (select id from B)
===>
select * from A left join B on a.id=b.id where b.id is null
 
關鍵是為什么in很快,not in很慢,
首先,not in並非是我們想象中的那樣,subquery只求一次值,然后外層select判斷是否in這個集合。
事實上,subquery會被執行N次。(好吧,瘋了吧,然而我沒有想清楚,為什么不能(設計成)執行一次就OK了。)
參考:https://stackoverflow.com/questions/12728654/why-is-this-mysql-query-with-the-not-in-statement-so-slow/12728981#12728981
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM