子查詢就是在一條查詢語句中還有其它的查詢語句,主查詢獲得的結果依賴於子查詢的結果。html
子查詢的子語句能夠在一條sql語句的FROM,JOIN,和WHERE後面,本文主要針對在WHERE後面使用子查詢與錶鏈接查詢的性能作出一點分析。mysql
對於錶鏈接查詢和子查詢性能的討論衆說紛紜,廣泛認爲的是錶鏈接查詢的性能要高於子查詢。本文將從實驗的角度,對這兩種查詢的性能作出驗證,並就實驗結果分析兩種查詢手段的執行流程對性能的影響。程序員
首先準備兩張表sql
1,訪問日誌表mm_log有150829條記錄(相關sql文件已放在文章結尾的連接中)。緩存
2,用戶表mm_member有373條記錄(相關sql文件已放在文章結尾的連接中)。性能
如今要求咱們根據這兩張表查出2017-02-06那天有那些用戶登陸過系統。spa
咱們先來看一下使用錶鏈接查詢3d
SELECT SQL_NO_CACHE mm.* FROM mm_member mm JOIN mm_log ml ON mm.id = ml.member_id WHERE ml.access_time LIKE '%2017-02-06%' GROUP BY ml.member_id;
這裏使用了 SQL_NO_CACHE 是由於要屢次執行這條sql語句,並計算出這條sql查詢所耗費的平均時間,因此要關掉mysql的查詢緩存,防止屢次執行從緩存中讀取數據。日誌
mm.*是取GROUP BY ml.member_id分組後的諸多臨時表的第一行數據,相關用法及原理請參見個人另外一篇博客(http://www.cnblogs.com/cdf-opensource-007/p/6502556.html)code
對這條sql語句執行了10次,查詢所耗費的平均時間在0.120s左右。
查詢結果:(一共有5個用戶訪問過系統)
至於以上這條sql的執行流程已經在前幾篇博客中描述的很詳細了,這裏就再也不作敘述了。
下面使用WHERE後使用子查詢的方式實現
SELECT SQL_NO_CACHE mm.username FROM mm_member mm WHERE mm.id IN(SELECT ml.member_id FROM mm_log ml WHERE ml.access_time LIKE '%2017-02-06%' GROUP BY ml.member_id);
當我第一次運行這條sql語句的時候,等了十幾秒一直沒有結果,我覺得個人電腦死機,可Navicat顯示處理中,最後40多秒的時候才運行出結果,接連運行了好屢次在都是41秒左右出結果。
咱們看到執行結果同上。那麼使用子查詢的性能到底低在哪裏呢?
個人第一種推測是子語句:SELECT ml.member_id FROM mm_log ml WHERE ml.access_time LIKE '%2017-02-06%' GROUP BY ml.member_id耗費了大量的查詢時間,由於mm_log這張表中有150829條記錄。
把子語句單拿出來運行一下,發現子語句的運行時間也就在0.111s左右。
SELECT SQL_NO_CACHE member_id FROM mm_log ml WHERE ml.access_time LIKE '%2017-02-06%' GROUP BY ml.member_id;
這就說明個人第一種推測是不合理的。
那就分析下在WHERE後使用子查詢IN的執行原理:
1,IN後面跟的子查詢語句的執行結果只能有一列是用來和IN前面的主表的字段匹配的,在這裏指的是mm.id。
2,一條帶有子查詢的sql語句首先執行的是子語句,主表的數據行按照IN前面主表的字段依次跟子查詢的結果進行匹配,子查詢中結果中有該數據行對應字段的值,則返回true,該行被WHERE篩選出來。沒有則返回false,該行不被篩選。
3,那麼按照2的說法,子查詢的效率應該也不低啊,子語句的耗時在0.111s左右,並且主表mm_member和子語句的查詢結果相匹配的次數,確定是要少於錶鏈接查詢時數據行間匹配的次數的,但實驗結果顯示使用子查詢的性能確實很低。
因此我有了第二種推測,主表mm_member數據行的每一行在與IN後面子語句的結果相匹配時,子語句都會從新執行一次,也就是說子語句第一次執行時,不會在內存中有緩存。這相似與使用了兩個FOR循環嵌套,外層的FOR循環每拿出一個值,內層的FOR循環都要遍歷一次。
那麼根據以上的推測,拿主表mm_member的數據行數乘以子語句的執行時間就應該是整個查詢的時間。
mm_member的數據行數:373
屢次執行子語句算出平均時間在:0.111s
整個查詢耗時的理論時間:41.403s
屢次執行整個查詢得出實際查詢時間的平均值:40.834s
計算偏差:(理論值-實際值)÷理論值 = 1.37%
偏差仍是在能夠接受的範圍內的,能夠證實以上的推測。
根據以上的實驗,咱們能夠得出的結論是,錶鏈接查詢的性能是要高於子查詢的。
另外,對於在子查詢中使用IN的性能高仍是是用EXITS的性能高,有一種廣泛的說法是:
1,在外表大,內表小,外表中有索引的狀況下,使用IN。
2,在外表小,內表大,內表中有索引的狀況下,使用EXITS。
先介紹一下EXITS的用法,恰好本例符合外表小內表大的狀況,就以本例介紹一下。看下SQL:
SELECT SQL_NO_CACHE mm.* FROM mm_member mm WHERE EXISTS(SELECT * FROM mm_log ml WHERE mm.id = ml.member_id AND ml.access_time LIKE '%2017-02-06%');
EXITS又簡稱代入查詢,就是把主表的每一行代入子表的每一行進行檢驗,一旦子表中有符合的數據行就返回true,就能夠取得主表中代入檢驗的那一行數據,不然返回false,不能夠取得主表中代入檢驗的那一行數據。同IN不一樣的是,EXITS後的子語句不查詢出結果,因此說SELECT後面的字段沒有意義,通常使用*代替,因爲EXITS的這種機制,當子表數據量比較大且有冗餘字段的時候就頗有可能避免了對子表的全表掃描,不像IN那樣每次主表數據行來匹配都要進行全表掃描,並返回結果。因此說EXITS相似於兩個FOR循環嵌套時,內層的FOR循環裏面有 if(xxx){ break; }這種語法。
以上sql執行時間的平均在34秒左右,比使用IN要快上一些,可是跟錶鏈接查詢還不能比。
可是,在表與表之間沒有關聯關係時,就只能使用IN了。
sql 文件位置:http://pan.baidu.com/s/1gfLwIwr
最後說一點,咱們做爲程序員,研究問題仍是要仔細深刻一點的。當你對原理了解的有夠透徹,開發起來也就駕輕就熟了,不少開發中的問題和疑惑也就迎刃而解了,並且在面對其餘問題的時候也可作到舉一反三。固然在開發中沒有太多的時間讓你去研究原理,開發中要以實現功能爲前提,可等項目上線的後,你有大把的時間或者空餘的時間,你大可去刨根問底,深刻的去研究一項技術,爲以爲這對一名程序員的成長是很重要的事情。