使用存在1或存在的子查询*

我曾经写这样的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 

这是否真的有所作为?

不,这已经超过了数十亿次。 SQL Server是聪明的,知道它正被用于EXISTS,并且将NO DATA返回给系统。

Quoth微软: http ://technet.microsoft.com/en-us/library/ms189259.aspx?ppud =4

由EXISTS引入的子查询的select列表几乎总是由星号(*)组成。 没有理由列出列名,因为您只是testing是否存在符合子查询中指定条件的行。

另外,不要相信我? 尝试运行以下内容:

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

如果它实际上正在做SELECT列表,它会抛出一个零错误。 它不。

编辑:请注意,SQL标准实际上谈到这一点。

ANSI SQL 1992标准,第191页http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

3)案例:
a)如果<select list> “*”仅仅包含在一个<subquery> <exists predicate>中的<subquery>中,那么<select list>相当于一个<value expression> ,它是一个任意的<literal>

这种误解的原因大概是因为相信它最终会读到所有的列。 很容易看出情况并非如此。

 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能够使用可用的最窄索引来检查结果,尽pipe索引不包括所有列。 索引访问在半连接运算符下,意味着它可以在第一行返回时立即停止扫描。

所以很清楚上面的看法是错误的。

不过,来自查询优化器小组的Conor Cunningham在这里解释说,在这种情况下,他通常使用SELECT 1 ,因为它可以在编译查询时产生较小的性能差异。

QP将会把所有的*都放在pipe道中,并将它们绑定到对象(在这个例子中是列的列表)。 然后,由于查询的性质,它将删除不需要的列。

所以对于这样一个简单的EXISTS子查询:

SELECT col1 FROM MyTable WHERE EXISTS (SELECT * FROM Table2 WHERE MyTable.col1=Table2.col2) *将被扩展为一些潜在的大列列表,然后将确定EXISTS的语义不需要任何这些列,所以基本上所有这些都可以被删除。

SELECT 1 ”将避免在查询编译期间检查该表的任何不需要的元数据。

但是,在运行时,查询的两种forms是相同的,并且具有相同的运行时间。

我testing了四个可能的方式来expression这个查询在一个空列上有不同数量的列。 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 1SELECT *之间没有一致的赢家,两种方法之间的差异可以忽略不计。 尽pipeSELECT 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])) 

将debugging器附加到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代码分析器进行备份,该分析器显示了两种情况下非常不同的函数select,这两种情况消耗了两种情况的编译时间( 前15个函数1024列和前15个函数1列 )。

如果用户没有被授予对表中所有列的访问权限, SELECT 1SELECT *版本都会结束检查列权限并失败。

一个例子,我从堆上的谈话中分裂出来

 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时,显着的不同之SELECT some_not_null_col在于,它只是检查特定列的权限(尽pipe仍然加载所有的元数据)。 但是,如果随着基础表中的列数增加,任何事情都变得更小,这两个方法之间的百分比差异似乎不符合事实。

在任何情况下,我都不会急于将所有的查询改变为这种forms,因为它们之间的差别非常小,而且只在查询编译期间显而易见。 删除OPTION (RECOMPILE)以便后续执行可以使用caching计划给出了以下内容。

在这里输入图像描述

 +-------------+-----------+------------+-----------+--------------+ | 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 | +-------------+-----------+------------+-----------+--------------+ 

我使用的testing脚本可以在这里find

最好的方法是对两个版本进行性能testing,并查看两个版本的执行计划。 select一个有很多列的表。

在SQL Server中没有区别,在SQL Server中从来没有出现问题。 优化器知道它们是一样的。 如果你看执行计划,你会看到他们是相同的。

就我个人而言,我觉得很难相信他们不会优化到相同的查询计划。 但要了解你的具体情况,唯一的办法就是testing一下。 如果你这样做,请回报!

没有任何真正的区别,但可能会有一个非常小的性能影响。 作为一个经验法则,您不应该要求比您需要更多的数据。