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


88

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

这真的有区别吗?


1
您忘记了EXISTS(SELECT NULL FROM ...)。
顺便问

16
ps获得一个新的DBA。迷信在IT领域没有地位,尤其是在数据库管理中(来自以前的DBA !!)
Matt Rogish 09年

Answers:


135

不,SQL Server很聪明,并且知道它已用于EXISTS,并且不向系统返回任何数据。

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标准,第191页http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

3)情况:
a)如果<select list>“ *”仅包含在<subquery> 紧接在中的a中<exists predicate>,则<select list> a等于a <value expression> 是任意的<literal>


1
EXISTS1/0 的技巧甚至可以扩展到此SELECT 1 WHERE EXISTS(SELECT 1/0)...似乎比第二个步骤SELECT没有FROM子句的步骤更抽象
whytheq 2012年

1
@whytheq-或SELECT COUNT(*) WHERE EXISTS(SELECT 1/0)。甲SELECT没有FROM,就好像它是访问单个行的表(例如,类似于选择从SQL Server中处理dual在其他RDBMS表)
马丁·史密斯

@MartinSmith欢呼雀跃-因此,要点是先SELECT创建1行表,然后再执行其他任何操作,所以即使1/01行表仍在垃圾中EXISTS
whytheq 2012年

是否一直都是这种情况,还是在特定版本的SQL Server中引入了优化?
马丁·布朗

1
@MartinSmith TIL“报价”。感谢您修复它。
Gurwinder Singh

111

造成这种误解的原因大概是因为人们相信它将最终阅读所有专栏。显而易见,事实并非如此。

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

因此,很明显上述信念是错误的。

但是,来自Query Optimiser团队的Conor Cunningham在这里解释说,他通常SELECT 1在这种情况下使用,因为这可能会在查询的编译中产生较小的性能差异。

QP将*在管道的早期获取并扩展all ,并将它们绑定到对象(在本例中为列列表)。然后,由于查询的性质,它将删除不需要的列。

因此,对于EXISTS像这样的简单子查询:

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

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

但是,在运行时查询的两种形式将是相同的,并且将具有相同的运行时。

我测试了在具有不同列数的空表上表达此查询的四种可能方法。SELECT 1VS SELECT *VS SELECT Primary_KeyVS 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 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)

我发现,在大多数情况下该表具有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代码分析器支持,它显示了两种功能的选择截然不同,这两种情况都消耗了编译时间(“ 前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是,它仅结束了对该特定列的检查权限(尽管仍然为所有列加载元数据)。但是,这似乎不符合事实,因为随着基础表中列数的增加,如果任何方法变小,则这两种方法之间的百分比差异就会变大。

无论如何,我都不会急着将所有查询更改为这种形式,因为差异很小,只有在查询编译期间才明显。删除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   |
+-------------+-----------+------------+-----------+--------------+

我使用的测试脚本可以在这里找到


3
+1对于获得真实数据所付出的努力,此答案值得更多投票。
2012年

1
您知道这些统计信息是在哪个版本的SQL Server上生成的吗?
马丁·布朗

3
@MartinBrown-IIRC最初是2008年,尽管我最近在2012年重新进行了测试以获取最新编辑,但发现相同。
马丁·史密斯

8

最好的了解方法是对两个版本进行性能测试,并检查两个版本的执行计划。选择一个有很多列的表。


2
+1。不知道为什么这被否决了。我一直认为教一个人钓鱼比给他一条鱼要好。人们将如何学习任何东西?
Ogre Psalm10年

5

SQL Server中没有任何区别,SQL Server中从来没有问题。优化器知道它们是相同的。如果查看执行计划,您会发现它们是相同的。


1

就我个人而言,我很难相信它们不能针对相同的查询计划进行优化。但是了解您的特定情况的唯一方法是进行测试。如果这样做,请报告!


-1

没有任何实际差异,但是性能影响可能很小。根据经验,您不应要求提供更多的数据。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.