首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用Exists 1或Exists *的子查询

使用Exists 1或Exists *的子查询
EN

Stack Overflow用户
提问于 2009-10-20 21:30:47
回答 6查看 27.7K关注 0票数 92

我过去常常这样写我的EXISTS检查:

代码语言:javascript
复制
IF EXISTS (SELECT * FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Where Columns=@Filters
END

以前的一位数据库管理员告诉我,当我执行EXISTS子句时,使用SELECT 1而不是SELECT *

代码语言:javascript
复制
IF EXISTS (SELECT 1 FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Columns=@Filters
END

这真的有区别吗?

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2009-10-21 05:37:37

不,SQL Server是智能的,它知道它正用于EXISTS,并且不向系统返回任何数据。

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

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

要检查自己,请尝试运行以下命令:

代码语言:javascript
复制
SELECT whatever
  FROM yourtable
 WHERE EXISTS( SELECT 1/0
                 FROM someothertable 
                WHERE a_valid_clause )

如果它真的对选择列表做了一些操作,它会抛出一个零错误的div。事实并非如此。

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

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

3)案例:

a)如果<select list> "*“只是包含在直接包含在<exists predicate>中的<subquery>中,那么该<select list>等价于任意<literal><value expression>

票数 138
EN

Stack Overflow用户

发布于 2011-05-26 22:36:39

产生这种误解的原因可能是因为相信它最终会读取所有列。很容易看出,事实并非如此。

代码语言:javascript
复制
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能够使用可用的最窄索引来检查结果,尽管该索引并不包括所有列。索引访问使用semi join操作符,这意味着只要返回第一行,它就可以停止扫描。

由此可见,上述观点显然是错误的。

然而,来自查询优化器团队的Conor Cunningham解释说,在这种情况下,他通常使用SELECT 1,因为它可以在查询的编译中产生很小的性能差异

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)在循环中运行查询,并测量了平均每秒执行的次数。结果如下

代码语言:javascript
复制
+-------------+----------+---------+---------+--------------+
| 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 *之间没有一致的赢家,两种方法之间的差异可以忽略不计。不过,SELECT Not Null colSELECT PK看起来确实更快一些。

随着表中列数的增加,所有四个查询的性能都会降低。

由于表是空的,这种关系似乎只能通过列元数据的数量来解释。对于COUNT(1),很容易从下面的过程中看到它在过程中的某个时候被重写为COUNT(*)

代码语言:javascript
复制
SET SHOWPLAN_TEXT ON;

GO

SELECT COUNT(1)
FROM master..spt_values

它给出了以下计划

代码语言:javascript
复制
  |--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进程,并在执行以下代码时随机中断

代码语言:javascript
复制
DECLARE @V int 

WHILE (1=1)
    SELECT @V=1 WHERE EXISTS (SELECT 1 FROM ##T) OPTION(RECOMPILE)

我发现,在表有1,024列的情况下,大部分时间调用堆栈看起来像下面这样,这表明即使在使用SELECT 1时,它也确实花费了很大一部分时间来加载列元数据(对于表有1列随机中断的情况,在10次尝试中没有命中调用堆栈的这一部分)。

代码语言:javascript
复制
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 

此手动分析尝试由VS2012代码分析器支持,它显示了两种情况下非常不同的函数选择,消耗了编译时间(Top 15 Functions 1024 columnsTop 15 Functions 1 column)。

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

我从the heap上的一次对话中抄袭的一个例子

代码语言:javascript
复制
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),以便后续执行可以使用缓存的计划,结果如下。

代码语言:javascript
复制
+-------------+-----------+------------+-----------+--------------+
| 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

票数 118
EN

Stack Overflow用户

发布于 2009-10-20 21:32:56

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

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1597442

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档