1. 程式人生 > >查詢在一張表不在另外一張表的記錄及效率探究

查詢在一張表不在另外一張表的記錄及效率探究

tro 圖片 刪除 rom 表連接 ren open 方式 mod

在我做項目的時候遇到一個需求,要將存在於表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
大表在前,小表在後。

註意

  1. 存儲過程循環插入比普通方式插入數據慢很多倍。
  2. 索引可以有效提高搜索效率。
  3. 不是所有的子查詢都比連接慢的。

參考文檔

  1. MySQL 5.6的優化器改進
  2. mysql(4)—— 表連接查詢與where後使用子查詢的性能分析。

查詢在一張表不在另外一張表的記錄及效率探究