SQL Server-聚焦IN VS EXISTS VS JOIN性能分析(十九)


前言

本節我們開始講講這一系列性能比較的終極篇IN VS EXISTS VS JOIN的性能分析,前面系列有人一直在說場景不夠,這里我們結合查詢索引列、非索引列、查詢小表、查詢大表來綜合分析,簡短的內容,深入的理解,Always to review the basics。

IN VS EXISTS VS JOIN性能分析

我們繼續創建測試表,如下

CREATE SCHEMA [compare]
CREATE TABLE t_outer (
        id INT NOT NULL PRIMARY KEY,
        val1 INT NOT NULL,
        val2 INT NOT NULL
)
CREATE TABLE t_inner (
        id INT NOT NULL PRIMARY KEY,
        val1 INT NOT NULL,
        val2 INT NOT NULL
)
CREATE TABLE t_smallinner (
        id INT NOT NULL PRIMARY KEY,
        val1 INT NOT NULL,
        val2 INT NOT NULL
)
GO
CREATE INDEX ix_outer_val1 ON [compare].t_outer (val1)
CREATE INDEX ix_inner_val1 ON [compare].t_inner (val1)
CREATE INDEX ix_smallinner_val1 ON [compare].t_smallinner (val1)

創建三個表即t_outer、t_inner、t_smaler同時將三個表中的列val1創建索引而對t_smaller表中的val2未創建索引,下面我們開始插入測試數據

USE TSQL2012
GO

DECLARE @num INT
SET @num = 1
WHILE @num <= 100000
BEGIN
        INSERT
        INTO    [compare].t_inner
        VALUES  (@num, RAND() * 100000000, RAND() * 100000000)
        INSERT
        INTO    [compare].t_outer
        VALUES  (@num, RAND() * 100000000, RAND() * 100000000)
        SET @num = @num + 1
END
GO

對t_inner和t_outer分別插入10萬條隨機數據,然后去取t_outer表中最后100條數據插入到表t_smaller中

USE TSQL2012
GO

INSERT
INTO    [compare].t_smallinner
SELECT  TOP 100 
        ROW_NUMBER() OVER (ORDER BY id DESC),
        val1,
        val2
FROM    [compare].t_outer
ORDER BY
        id DESC
GO

表以及測試數據創建完畢,下面我們開始一個一個分析。

(1)IN性能分析(在大表上查詢索引列val1)

SELECT  val1
FROM    [compare].t_outer o
WHERE   val1 IN
        (
        SELECT  val1
        FROM    [compare].t_inner
        )

 

我們將上述查詢計划示意圖過程簡短描述成如下:

整個查詢耗費時間如下:

此時整個查詢時間耗費70毫秒,對於10萬條數據來說算是非常快的了,因為此時我們在t_inner表和t_outer表上的列val1都建立了索引,所以此時選擇Stream Aggregate來進行過濾去除對於t_outer表上的val1中對應的t_inner表上的val1的重復值。到底是怎么去除重復的呢?它會記錄重復的最后一個值,當再有值被找到,此時將無法通過。上述之所以查詢非常快的原因在於輸入行已經提前進行了預排序。最后得到的兩個表的結果集進行Merge Join,進行Merge Join時,它會初始化一個變量並將指針指向加入的兩個列的最小值,然后返回兩個表結果集中匹配到的值,然后將指針指向下一個兩個索引列中的存在的值,否則跳過不匹配的值,一直到完成,當進行如下查詢時和上述查詢計划是一致的。

SELECT  o.val1
FROM    [compare].t_outer o
JOIN    (
        SELECT  DISTINCT val1
        FROM    [compare].t_inner
        ) i
ON      i.val1 = o.val1

(1)EXISTS性能分析(在大表上查詢索引列val1)

我們通過如下查詢來分析EXISTS

USE TSQL2012
GO

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT  val1
FROM    [compare].t_outer o
WHERE   EXISTS (
        SELECT  1
        FROM    [compare].t_inner s
        WHERE   s.val1 = o.val1
        )

上述我們能夠很清楚的知道EXISTS查詢計划和IN是一致的,信不信由你,當下次面試再問二者性能的問題時,可千萬別說EXISTS性能高於IN,這是錯誤的,上述我們已經分析得出其實是一樣的。如果你仍是覺得EXISTS性能高於IN,請用事實證明。上述我們一直演示的是查詢索引列val1,那要是在非索引列val2上查詢會怎樣呢。

(2)IN性能分析(在大表上查詢非索引列val2)

USE TSQL2012
GO

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT  val2
FROM    [compare].t_outer
WHERE   val2 IN
        (
        SELECT  val2
        FROM    [compare].t_inner
        )

我們再來分析下查詢計划

我們重點看看Hash Match(Left Semi Join),此時對t_outer表上的值建立哈希表,然后t_inner表中每一行值來探測該哈希表,接着通過Left Semi Join來匹配值,如果匹配到值,此時匹配到的值會立即從哈希表中移除,最終哈希表將逐漸縮小。接着我們再來看EXISTS。

(2)EXISTS性能分析(在大表上查詢非索引列val2) 

USE TSQL2012
GO

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT  val2
FROM    [compare].t_outer o
WHERE   EXISTS (
        SELECT  1
        FROM    [compare].t_inner s
        WHERE   s.val2 = o.val2
        )

此時我們看到無論是查詢索引列還是非索引列EXISTS和IN在查詢計划和耗費時間幾乎完全是一致的,到這里我們針對討論的是大表10萬條數據,下面我們會討論在小表t_smaller中有關二者的查詢。接下來我們看看利用JOIN在索引列上進行查詢。

(2)JOIN性能分析(在大表上查詢非索引列val2)

USE TSQL2012
GO

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT  o.val2
FROM    [compare].t_outer o
JOIN    (
        SELECT  DISTINCT val2
        FROM    [compare].t_inner
        ) i
ON      i.val2 = o.val2

 

我們看到查詢耗費時間和查詢計划都和EXISTS、IN有不同,我們再來看看執行的順序。

與上述不同的是JOIN在兩個表聯合之前首先進行了Hash Match(Aggregate),也就是說和EXISTS、IN不同之處在於重復值的處理,對於EXISTS、IN來說直接將兩個表進行聯合然后通過LEFT Semi Join來進行過濾重復值,在此通過哈希匹配中的聚合來過濾去除重復值val2,Hash Match(Aggregare)建立了一個唯一的哈希表,所以很容易來過濾重復值,因為有重復值過來時唯一哈希表能夠探測到會產生值沖突,此時重復值都不會進入哈希表中。查詢引擎通過哈希表來探測t_outer中的值,最終返回匹配的值。普遍想法是JOIN性能比EXISTS、IN性能要好,上述我們在查詢非索引列時其查詢開銷和耗費時間卻比EXISTS、IN要高,所以相對來說JOIN對於查詢非索引列時其性能是比較低效的。接下來我們繼續來看看查詢小表t_smaller的情況。

(3)IN性能分析(在小表上查詢索引列val1)

我們查詢小表看看關於IN的查詢情況是怎樣的呢

USE TSQL2012
GO

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT  val1
FROM    [compare].t_outer o
WHERE   val1 IN
        (
        SELECT  val1
        FROM    [compare].t_smallinner
        )

 

因為數據只有100條的小表其查詢耗費時間當然非常少且查詢速度非常快,我們重點看看其查詢計划。此時合並結果集時不再是Merge Join代替的是遍歷整個索引,它會掃描整個t_smaller表來過濾重復值,當然僅僅只是查找在t_outer表上創建的索引列val1且是通過索引查找的方式。數據量小所以即使是遍歷整個索引也是非常快的。在EXISTS和JOIN中其執行計划結果和上述一致,下面我們再來看看查詢非索引列的情況。

(4)IN性能分析(在小表上查詢非索引列val2)

USE TSQL2012
GO

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT val2
FROM    [compare].t_outer o
WHERE   val2 IN
        (
        SELECT  val2
        FROM    [compare].t_smallinner
        )

 

此時我們看到查詢小表上非索引列val2和大表上的非索引列val2執行計划幾乎是一樣的,有一點不同的是在大表中建立哈希表是在外部查詢表中,在這里卻是在子查詢表中建立哈希表,這就是查詢引擎高明的地方,數據少時在小表上建立哈希表一來在哈希表中存儲的數據少即占用內存少,二來當匹配到值時就縮減哈希表的大小。我們再來看看JOIN的情況。

(4)JOIN性能分析(在小表上查詢非索引列val2)

USE TSQL2012
GO

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT  o.val2
FROM    [compare].t_outer o
JOIN    (
        SELECT  DISTINCT val2
        FROM    [compare].t_smallinner
        ) i
ON      i.val2 = o.val2

因為數據量小所以耗費時間短,這個我們可以忽略不看,我們還是看看查詢計划情況,此時利用Distinct Sort來消除重復的數據而不是利用哈希表,它會一次次的重建,可想而知性能的低下。分析到這里為止,我們看到在SQL Server中其實在有些情況下IN、EXISTS的查詢性能是高於JOIN的。還不相信嗎,我們再來看一個例子。

USE TSQL2012
GO

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT  val2
FROM    [compare].t_outer o
WHERE   EXISTS (
        SELECT  1
        FROM    [compare].t_smallinner s
        WHERE   s.val2 - o.val2 = 0
        )

因為查詢條件壓根就無法匹配導致哈希表都不會有,結果查詢引擎會使用Nested Loops(Left Semi Join)來進行全表掃描,此時耗費時間接近需要2秒。好了,到這里為止我們關於IN VS EXISTS VS JOIN的分析就已經完全結束,參考資料:【https://explainextended.com/2009/06/16/in-vs-join-vs-exists/】下面我們和前面一樣來對這三者下一個結論:

IN VS EXISTS VS JOIN性能分析結論:在查詢非索引列時,利用JOIN查詢性能低下,因為利用EXISTS和IN會直接利用半聯接來匹配哈希表,而JOIN需要先進行哈希聚合之后再進行完全JOIN,換句話說,EXISTS和IN只需一步操作就完成,而JOIN需要兩步操作來完成,當然對於有索引的前提下,數據量巨大的話,利用JOIN其性能同樣也是非常高效的。而IN和EXISTS的性能是一樣的,至於為何推薦用EXISTS的原因在於基於EXISTS是三值邏輯,而IN是兩值邏輯,利用EXISTS來查詢比IN更加靈活,安全、保險,而且大多數情況下利用IN來查詢都可以利用EXISTS來代替查詢。

總結

本節我們討論了IN VS EXISTS VS JOIN的性能比較,至此關於所有IN/NOT IN VS EXISTS/NOT EXISTS VS JOIN/LEFT JOIN..IS NULL的性能分析到此告一段落,接下來我們將會講述Stream Aggregate VS Hash Match Aggregate,敬請期待,簡短的內容,深入的理解,我們下節再會。


免責聲明!

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



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