この誤解の理由は、おそらくすべての列を読み取ることになると考えられているためです。これが当てはまらないことは簡単にわかります。
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'
計画を与える

これは、インデックスにすべての列が含まれていないにもかかわらず、SQL Serverが利用可能な最も狭いインデックスを使用して結果を確認できたことを示しています。インデックスアクセスは、準結合演算子の下にあります。つまり、最初の行が返されるとすぐにスキャンを停止できます。
したがって、上記の信念が間違っていることは明らかです。
ただし、クエリオプティマイザーチームのConor Cunninghamは、クエリのコンパイルでパフォーマンスにわずかな違いが生じる可能性があるため、この場合に通常使用することを説明します。SELECT 1
QPは*
パイプラインの早い段階ですべてのを取得して展開し、それらをオブジェクト(この場合は列のリスト)にバインドします。次に、クエリの性質上、不要な列を削除します。
したがって、次のEXISTS
ような単純なサブクエリの場合:
SELECT col1 FROM MyTable WHERE EXISTS
(SELECT * FROM Table2 WHERE
MyTable.col1=Table2.col2)
*
いくつかの潜在的に大きな列のリストに展開されますし、の意味があると判断される
EXISTS
ので、基本的にすべてのそれらの除去することができ、これらの列のいずれかを必要としません。
" SELECT 1
"は、クエリのコンパイル中にそのテーブルの不要なメタデータを調べる必要を回避します。
ただし、実行時には、クエリの2つの形式は同じであり、実行時間は同じです。
さまざまな数の列を持つ空のテーブルでこのクエリを表現する4つの方法をテストしました。SELECT 1
対SELECT *
対SELECT Primary_Key
対 SELECT Other_Not_Null_Column
。
クエリをループOPTION (RECOMPILE)
で実行し、1秒あたりの平均実行数を測定しました。以下の結果

+-------------+----------+---------+---------+--------------+
| 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 *
との間に一貫した勝者はなく、2つのアプローチの違いは無視できます。わずかに速いけれども表示されます。SELECT Not Null col
SELECT PK
テーブルの列数が増えると、4つのクエリすべてのパフォーマンスが低下します。
テーブルが空であるため、この関係は列のメタデータの量によってのみ説明できるようです。以下のために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)
私は、例の表は、最も時間の1024列、確かにしても、時間ローディング列メタデータの大部分を費やしていることを示す以下のようなもののようなコールスタックルックス有する場合ことを発見し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
kernel32.dll!_BaseThreadStart@8() + 0x37 bytes
この手動プロファイリングの試みは、VS 2012コードプロファイラーによってバックアップされます。このプロファイラーは、2つのケース(上位15関数1024列と上位15関数1列)のコンパイル時間を消費する関数の非常に異なる選択を示しています。
SELECT 1
とSELECT *
バージョンの両方で列の権限のチェックが終了し、ユーザーがテーブル内のすべての列へのアクセスを許可されていない場合は失敗します。
ヒープでの会話から書いた例
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
は、特定の列のアクセス許可のチェックのみが終了することです(ただし、すべてのメタデータをロードします)。ただし、基礎となるテーブルの列の数が増えるにつれて何かが小さくなると、2つのアプローチのパーセンテージの差が大きくなるため、これは事実と一致しないようです。
いずれにせよ、私は急いですべてのクエリをこの形式に変更することはしません。違いはごくわずかであり、クエリのコンパイル中にのみ明らかであるためです。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 |
+-------------+-----------+------------+-----------+--------------+
私が使用したテストスクリプトはここにあります