的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)
在循環中運行了查詢,並測量了每秒平均執行次數。結果如下
+-------------+----------+---------+---------+--------------+
| 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 1
和SELECT *
和兩種方法之間的差異可以忽略不計之間沒有常勝將軍。 SELECT Not Null col
和SELECT 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 1
和SELECT *
版本都會檢查列權限,如果用戶未被授予對錶中所有列的訪問權限,則會失敗。
我從談話那兒剽竊上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)
,以便後續執行可以使用緩存計劃給出以下內容。
+-------------+-----------+------------+-----------+--------------+
| 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
你忘了EXISTS(SELECT NULL FROM ...)。這是最近詢問 – 2009-10-20 21:38:47
p.s.得到一個新的DBA。迷信在IT中沒有地位,尤其是在數據庫管理方面(來自前DBA!) – 2009-10-20 21:54:16