查詢在一張表不在另外一張表的記錄及效率探究
在我做項目的時候遇到一個需求,要將存在於表ta而不存在於表tb中的數據查詢出來。
記錄使用的方法和探討效率。
數據準備
創建表ta,並且使用存儲過程插入13000條數據,在我的機器上運行時間: 346.719s。如果覺得插入的速度比較慢,可以直接導入我建好的表,百度雲地址 http://pan.baidu.com/s/1dFtovg1 ,裏面已經有數據了,直接導入sql執行即可,這樣比用存儲過程要快很多。
DROP TABLE IF EXISTS ta; CREATE TABLE `ta` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, PRIMARY
KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=13000 DEFAULT CHARSET=utf8; DROP PROCEDURE IF EXISTS ta_insert; DELIMITER $$ CREATE PROCEDURE ta_insert() MODIFIES SQL DATA BEGIN SET @i=1; SET @max=13000; WHILE @i<@max DO INSERT INTO `ta` VALUES (); SET @i = @i + 1; END WHILE; end $$ CALL ta_insert();
創建表tb,並且使用存儲過程插入10000條數據,在我的機器上運行時間: 224.102s。
DROP TABLE IF EXISTS tb;
CREATE TABLE `tb` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8000 DEFAULT CHARSET=utf8;
DROP PROCEDURE IF EXISTS tb_insert;
DELIMITER $$
CREATE PROCEDURE tb_insert()
MODIFIES SQL DATA
BEGIN
SET @i=1;
SET @max=8000;
WHILE @i<@max DO
INSERT INTO `tb` VALUES ();
SET @i = @i + 1;
END WHILE;
end $$
CALL tb_insert();
子查詢
使用NOT IN
,ta表中的每一個id值都要去與tb表中的id匹配,匹配到就停止,也就是說,存在於ta表而不存在於tb表的id值需要與所有tb表中的id值進行匹配。
執行子查詢時,MYSQL需要創建臨時表,查詢完畢後再刪除這些臨時表,所以,子查詢的速度會受到一定的影響,這裏多了一個創建和銷毀臨時表的過程。
平均時間為 0.04s。
SELECT ta.id FROM ta WHERE ta.id IN (SELECT id FROM tb)
左連接
使用 LEFT JOIN
,ta表左連接tb表,而存在於ta表不存在於tb表中的字段為NULL
,於是我們可以通過判斷WHERE tb.id IS NULL
來找到存在於ta表而不存在於tb表的id值。
平均時間是 0.06s。
SELECT ta.id FROM ta LEFT JOIN tb ON ta.id = tb.id WHERE tb.id IS NULL
效率之謎
版本問題?
按理來說,連接應該比子查詢要快,但是在我進行試驗的時候發現卻不是這樣的,子查詢居然還比連接要快。
搜索了解到
對於類似NOT IN這樣的子查詢,也能受益於subquery materialize,將子查詢的結果集cache到臨時表裏,使用hashindex來進行檢索;物化的子查詢可以看到select_type字段為SUBQUERY,而在MySQL5.5裏為DEPENDENT SUBQUERY
可能是版本原因,我用的是mysql5.7,可能做了優化。
於是使用mysql5.5再次測試。
發現子查詢和左連接的查詢時間都在0.12s附近,還是不能說明連接比子查詢高效。進一步猜測,我的數據組織格式是否出現了問題,於是使用上面百度雲盤連接中的mm_member表和mm_log表(這是 mysql(4)—— 表連接查詢與where後使用子查詢的性能分析。 提供的數據) 。
猜測驗證
子查詢
SELECT mm_member.id FROM mm_member WHERE mm_member.id NOT IN (SELECT DISTINCT mm_log.member_id FROM mm_log)
左連接
SELECT mm_member.id FROM mm_member LEFT JOIN (SELECT DISTINCT mm_log.member_id FROM mm_log ) AS mm
ON mm.member_id = mm_member.id WHERE mm.member_id IS NULL
mysql5.7
子查詢為1.1s左右,左連接為1.55s,子查詢依然速度較快。
mysql5.5
子查詢為48s左右,左連接為1.4s,將近34倍的差距,由此印證上面引用的那部分,mysql5.7確實已經多子查詢做了優化,使其達到了逼近左連接的效率。
那為什麽我自己所建立的表無法體現版本的這種性能差別?
猜測應該是數據類型的原因,可能int類型的查詢效率已經都優化好了。
網傳最高效
不太清楚其中的原理,並且在我的測試中性能跟連接差不多。
SELECT id FROM ta WHERE (SELECT COUNT(1) AS num FROM tb WHERE ta.id = tb.id) = 0
而且網上流傳的版本((數據庫篇) SQL查詢~ 存在一個表而不在另一個表中的數據)為
select * from B where (select count(1) as num from A where A.ID = B.ID) = 0
應該是
select * from A where (select count(1) as num from B where A.ID = B.ID) = 0
大表在前,小表在後。
註意:
- 存儲過程循環插入比普通方式插入數據慢很多倍。
- 索引可以有效提高搜索效率。
- 不是所有的子查詢都比連接慢的。
參考文檔:
- MySQL 5.6的優化器改進
- mysql(4)—— 表連接查詢與where後使用子查詢的性能分析。
查詢在一張表不在另外一張表的記錄及效率探究