2009-10-20 75 views
73

我以前寫我EXISTS檢查這樣的:子查詢使用已存在1或者雖然存在*

IF EXISTS (SELECT * FROM TABLE WHERE [email protected]) 
BEGIN 
    UPDATE TABLE SET ColumnsX=ValuesX WHERE Where [email protected] 
END 

一個DBA的前世的告訴我,當我做一個EXISTS條款,使用SELECT 1代替SELECT *

IF EXISTS (SELECT 1 FROM TABLE WHERE [email protected]) 
BEGIN 
    UPDATE TABLE SET ColumnsX=ValuesX WHERE [email protected] 
END 

這是否真的有所作爲?

+1

你忘了EXISTS(SELECT NULL FROM ...)。這是最近詢問 – 2009-10-20 21:38:47

+13

p.s.得到一個新的DBA。迷信在IT中沒有地位,尤其是在數據庫管理方面(來自前DBA!) – 2009-10-20 21:54:16

回答

110

不,這已經涵蓋了bazillion次。 SQL Server非常聰明,知道它正在用於EXISTS,並將NO DATA返回給系統。

答曰微軟: http://technet.microsoft.com/en-us/library/ms189259.aspx?ppud=4

通過引入子查詢 的選擇列表中存在幾乎總是 由星號(*)的。 沒有理由列出列名,因爲 您只是在測試 是否滿足 子查詢中指定的條件。

另外,不要相信我?嘗試運行以下內容:

SELECT whatever 
    FROM yourtable 
WHERE EXISTS(SELECT 1/0 
       FROM someothertable 
       WHERE a_valid_clause) 

如果實際上正在使用SELECT列表,它會拋出一個由零錯誤的div。它沒有。

編輯:請注意,SQL標準實際上談到這一點。

ANSI SQL 1992年標準,第191 http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

3)案例:
a)如果<select list> 「*」 被簡單地包含在<subquery>該 立即包含在<exists predicate>,那麼<select list>是 相當於<value expression> 這是一個任意<literal>

+56

在EXISTS子句中爲1/0。 – gbn 2009-10-21 20:51:44

+1

帶有1/0的'EXISTS'技巧甚至可以擴展到這個'SELECT 1 WHERE EXISTS(SELECT 1/0)'...似乎是一個更抽象的步驟,然後第二個'SELECT'沒有'FROM'子句 – whytheq 2012-09-13 10:50:58

+1

@whytheq - 或'SELECT COUNT(*)WHERE EXISTS(SELECT 1/0)'。 SQL Server中沒有'FROM'的SELECT'被視爲訪問單個行表(例如,類似於從其他RDBMS中的「dual」表中選擇) – 2012-11-08 21:22:24

7

最好的方法是對兩個版本進行性能測試並檢查兩個版本的執行計劃。選擇一個有很多列的表格。

+2

+1。不知道爲什麼這是低估。我一直認爲教一個男人去釣魚更好,而不是隻給他一條魚。人們如何學習任何東西? – 2010-06-23 17:40:04

+1

+1自己動手,忠告歡迎。 – 2010-09-01 19:06:22

-1

沒有任何實質性差異,但可能會有非常小的性能影響。作爲一個經驗法則,你不應該要求比你需要更多的數據。

1

我個人覺得非常非常難以相信他們不會優化到相同的查詢計劃。但要了解您的具體情況,唯一的方法就是測試它。如果你這樣做,請回報!

5

SQL Server沒有什麼區別,它在SQL Server中從來不是問題。優化器知道它們是一樣的。如果你看執行計劃,你會看到它們是相同的。

89

這種誤解的原因大概是因爲認爲它最終會讀取所有列。很容易看出情況並非如此。

CREATE TABLE T 
(
X INT PRIMARY KEY, 
Y INT, 
Z CHAR(8000) 
) 

CREATE NONCLUSTERED INDEX NarrowIndex ON T(Y) 

IF EXISTS (SELECT * FROM T) 
    PRINT 'Y' 

給人計劃

Plan

這表明的SQL Server能夠使用現有的最窄指數儘管該指數不包括所有列,檢查的結果。索引訪問位於半連接運算符下,這意味着只要返回第一行就可以停止掃描。

所以很清楚上面的觀點是錯誤的。

但是從查詢優化器團隊康納爾坎寧安解釋here,他通常在這種情況下使用SELECT 1,因爲它可以在查詢的編譯稍作性能差異

的QP將採取擴大所有*的 在管線中較早並將其綁定到 對象(在這種情況下, 列的列表)。然後它將刪除 不需要的列,因爲查詢的性質爲 。

因此,對於一個簡單的EXISTS子查詢像 這樣:

SELECT col1 FROM MyTable WHERE EXISTS (SELECT * FROM Table2 WHERE MyTable.col1=Table2.col2)*將 擴大到一些潛在的巨大 列列表,然後將 確定的 EXISTS語義不需要任何那些 列,所以基本上所有的人都可以刪除 。

SELECT 1」將避免必須在查詢編譯期間檢查 表的任何不需要的元數據。

但是,在運行時,查詢的兩種形式 將是相同的,並且 具有相同的運行時。

我測試了四種可能的方式來在具有各種列數的空表上表達此查詢。 SELECT 1 vs SELECT * vs SELECT Primary_Key vs SELECT Other_Not_Null_Column

我使用OPTION (RECOMPILE)在循環中運行了查詢,並測量了每秒平均​​執行次數。結果如下

enter image description here

+-------------+----------+---------+---------+--------------+ 
| Num of Cols | *  | 1 | PK | Not Null col | 
+-------------+----------+---------+---------+--------------+ 
| 2   | 2043.5 | 2043.25 | 2073.5 | 2067.5  | 
| 4   | 2038.75 | 2041.25 | 2067.5 | 2067.5  | 
| 8   | 2015.75 | 2017 | 2059.75 | 2059   | 
| 16   | 2005.75 | 2005.25 | 2025.25 | 2035.75  | 
| 32   | 1963.25 | 1967.25 | 2001.25 | 1992.75  | 
| 64   | 1903  | 1904 | 1936.25 | 1939.75  | 
| 128   | 1778.75 | 1779.75 | 1799 | 1806.75  | 
| 256   | 1530.75 | 1526.5 | 1542.75 | 1541.25  | 
| 512   | 1195  | 1189.75 | 1203.75 | 1198.5  | 
| 1024  | 694.75 | 697  | 699  | 699.25  | 
+-------------+----------+---------+---------+--------------+ 
| Total  | 17169.25 | 17171 | 17408 | 17408  | 
+-------------+----------+---------+---------+--------------+ 

可以看出有SELECT 1SELECT *和兩種方法之間的差異可以忽略不計之間沒有常勝將軍。 SELECT Not Null colSELECT PK確實出現了稍快。

隨着表中列數的增加,所有四個查詢的性能都會降低。

由於表格爲空,此關係似乎只能由列元數據的數量來解釋。對於COUNT(1)很容易看出,在下面的過程中的某個時刻,這會被重寫爲COUNT(*)

SET SHOWPLAN_TEXT ON; 

GO 

SELECT COUNT(1) 
FROM master..spt_values 

這樣做具有以下計劃

|--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1004],0))) 
     |--Stream Aggregate(DEFINE:([Expr1004]=Count(*))) 
      |--Index Scan(OBJECT:([master].[dbo].[spt_values].[ix2_spt_values_nu_nc])) 

附加一個調試器到SQL Server進程,並隨機斷裂,同時執行以下

DECLARE @V int 

WHILE (1=1) 
    SELECT @V=1 WHERE EXISTS (SELECT 1 FROM ##T) OPTION(RECOMPILE) 

的我發現,在情況表在大多數時候,調用堆棧看起來像下面這樣,表示它確實花費了很大比例的時間加載列元數據,甚至有時候ÑSELECT 1被使用(對於其中表具有1組的列隨機斷裂沒有命中的調用堆棧的該位在10次的情況下)

sqlservr.exe!CMEDAccess::GetProxyBaseIntnl() - 0x1e2c79 bytes 
sqlservr.exe!CMEDProxyRelation::GetColumn() + 0x57 bytes 
sqlservr.exe!CAlgTableMetadata::LoadColumns() + 0x256 bytes  
sqlservr.exe!CAlgTableMetadata::Bind() + 0x15c bytes 
sqlservr.exe!CRelOp_Get::BindTree() + 0x98 bytes 
sqlservr.exe!COptExpr::BindTree() + 0x58 bytes 
sqlservr.exe!CRelOp_FromList::BindTree() + 0x5c bytes 
sqlservr.exe!COptExpr::BindTree() + 0x58 bytes 
sqlservr.exe!CRelOp_QuerySpec::BindTree() + 0xbe bytes 
sqlservr.exe!COptExpr::BindTree() + 0x58 bytes 
sqlservr.exe!CScaOp_Exists::BindScalarTree() + 0x72 bytes 
... Lines omitted ... 
msvcr80.dll!_threadstartex(void * ptd=0x0031d888) Line 326 + 0x5 bytes C 
[email protected]() + 0x37 bytes 

本手冊分析企圖由VS 2012代碼探查備份這顯示了兩種情況下消耗編譯時間的非常不同的功能選擇(Top 15 Functions 1024 columns vs Top 15 Functions 1 column)。

SELECT 1SELECT *版本都會檢查列權限,如果用戶未被授予對錶中所有列的訪問權限,則會失敗。

我從談話那兒剽竊上the heap

CREATE USER blat WITHOUT LOGIN; 
GO 
CREATE TABLE dbo.T 
(
X INT PRIMARY KEY, 
Y INT, 
Z CHAR(8000) 
) 
GO 

GRANT SELECT ON dbo.T TO blat; 
DENY SELECT ON dbo.T(Z) TO blat; 
GO 
EXECUTE AS USER = 'blat'; 
GO 

SELECT 1 
WHERE EXISTS (SELECT 1 
       FROM T); 
/* ↑↑↑↑ 
Fails unexpectedly with 

The SELECT permission was denied on the column 'Z' of the 
      object 'T', database 'tempdb', schema 'dbo'.*/ 

GO 
REVERT; 
DROP USER blat 
DROP TABLE T 

一個例子所以有人會推測,使用SELECT some_not_null_col當未成年人明顯不同的是,它只捲起檢查對特定列的權限(儘管仍然加載元對全部)。然而,如果隨着基礎表中的列數增加,任何事情變得更小,這兩個方法之間的百分比差異似乎不符合事實。

在任何情況下,我都不會急於將所有的查詢改爲這種形式,因爲差異很小,只在查詢編譯期間顯而易見。刪除OPTION (RECOMPILE),以便後續執行可以使用緩存計劃給出以下內容。

enter image description here

+-------------+-----------+------------+-----------+--------------+ 
| Num of Cols |  *  |  1  | PK  | Not Null col | 
+-------------+-----------+------------+-----------+--------------+ 
| 2   | 144933.25 | 145292  | 146029.25 | 143973.5  | 
| 4   | 146084 | 146633.5 | 146018.75 | 146581.25 | 
| 8   | 143145.25 | 144393.25 | 145723.5 | 144790.25 | 
| 16   | 145191.75 | 145174  | 144755.5 | 146666.75 | 
| 32   | 144624 | 145483.75 | 143531 | 145366.25 | 
| 64   | 145459.25 | 146175.75 | 147174.25 | 146622.5  | 
| 128   | 145625.75 | 143823.25 | 144132 | 144739.25 | 
| 256   | 145380.75 | 147224  | 146203.25 | 147078.75 | 
| 512   | 146045 | 145609.25 | 145149.25 | 144335.5  | 
| 1024  | 148280 | 148076  | 145593.25 | 146534.75 | 
+-------------+-----------+------------+-----------+--------------+ 
| Total  | 1454769 | 1457884.75 | 1454310 | 1456688.75 | 
+-------------+-----------+------------+-----------+--------------+ 

The test script I used can be found here

+1

+1這個答案值得爲獲得真實數據所付出的努力多投票。 – Jon 2012-06-08 14:23:49

+1

是否知道生成這些統計信息的SQL Server版本? – 2015-02-09 11:14:17

+1

@MartinBrown - IIRC最初的2008雖然我最近在2012年爲最近的編輯重新進行了測試,結果發現相同。 – 2015-02-09 12:10:55