我曾经写过这样的EXISTS支票:
IF EXISTS (SELECT * FROM TABLE WHERE Columns=@Filters)
BEGIN
UPDATE TABLE SET ColumnsX=ValuesX WHERE Where Columns=@Filters
END
前一个DBA中的一位告诉我,当我执行EXISTS
条款时,请使用SELECT 1
代替SELECT *
IF EXISTS (SELECT 1 FROM TABLE WHERE Columns=@Filters)
BEGIN
UPDATE TABLE SET ColumnsX=ValuesX WHERE Columns=@Filters
END
这真的有所作为吗?
答案 0 :(得分:127)
不,SQL Server很聪明,并且知道它正用于EXISTS,并将NO DATA返回给系统。
Quoth Microsoft: http://technet.microsoft.com/en-us/library/ms189259.aspx?ppud=4
子查询的选择列表 几乎总是由EXISTS介绍 由星号(*)组成。有 没有理由列出列名,因为 你只是测试行是否 符合规定的条件 子查询存在。
要检查自己,请尝试运行以下内容:
SELECT whatever
FROM yourtable
WHERE EXISTS( SELECT 1/0
FROM someothertable
WHERE a_valid_clause )
如果它实际上是在使用SELECT列表做某事,它会抛出一个div为零的错误。它没有。
编辑:注意,SQL标准实际上是在讨论这个问题。
ANSI SQL 1992 Standard,第191页http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
3)案例:
a)如果<select list>
“*”仅包含在<subquery>
中 立即包含在<exists predicate>
中,然后是<select list>
相当于<value expression>
这是一个任意的<literal>
。
答案 1 :(得分:103)
这种误解的原因可能是因为他们相信它最终会阅读所有专栏。很容易看出情况并非如此。
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解释here他通常在这种情况下使用SELECT 1
,因为它可以在查询的编译中产生轻微的性能差异
QP将采用并扩展所有
*
在管道的早期并将它们绑定到 对象(在这种情况下,列表 列)。然后它会删除 由于性质的原因,不需要的列 查询。所以对于一个简单的
EXISTS
子查询,如 这样:
SELECT col1 FROM MyTable WHERE EXISTS (SELECT * FROM Table2 WHERE MyTable.col1=Table2.col2)
*
将是。{1}} 扩展到一些潜在的大 列列表然后它将是 确定了语义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)
我发现在大多数情况下表中有1,024列的情况下,调用堆栈看起来像下面的内容,表明即使在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代码分析器备份,该分析器显示了两种情况(Top 15 Functions 1024 columns与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 |
+-------------+-----------+------------+-----------+--------------+
答案 2 :(得分:7)
最好的方法是对两个版本进行性能测试,并查看两个版本的执行计划。选择一个包含大量列的表格。
答案 3 :(得分:5)
SQL Server没有任何区别,它在SQL Server中从来就不是问题。优化器知道它们是相同的。如果你看一下执行计划,你会发现它们完全相同。
答案 4 :(得分:1)
就我个人而言,我发现非常非常难以相信他们没有针对同一个查询计划进行优化。但在您的特定情况下了解的唯一方法是测试它。如果你这样做,请报告回来!
答案 5 :(得分:-1)
没有任何真正的区别,但可能会有非常小的性能影响。根据经验,您不应该要求比您需要的更多数据。