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

Raj*_*ore 84 sql t-sql sql-server

我以前写这样的EXISTS检查:

IF EXISTS (SELECT * FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Where Columns=@Filters
END
Run Code Online (Sandbox Code Playgroud)

前一个DBA中的一个告诉我,当我做一个EXISTS条款时,请使用SELECT 1而不是SELECT *

IF EXISTS (SELECT 1 FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Columns=@Filters
END
Run Code Online (Sandbox Code Playgroud)

这真的有所作为吗?

Mat*_*ish 129

不,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 )
Run Code Online (Sandbox Code Playgroud)

如果它实际上正在使用SELECT列表执行某些操作,则会抛出一个div为零的错误.它没有.

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

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

3)情况:
a)如果<select list>"*"简单地包含在一个直接包含在a中的<subquery> 那个中<exists predicate>,那么<select list> 它等同于<value expression> 任意一个<literal>.


Mar*_*ith 106

造成这种误解的原因可能是因为它相信它最终会读取所有列.很容易看出情况并非如此.

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'
Run Code Online (Sandbox Code Playgroud)

给出计划

计划

这表明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"将避免在查询编译期间检查该表的任何不需要的元数据.

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

我测试了在具有不同列数的空表上表达此查询的四种可能方式.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        |
+-------------+----------+---------+---------+--------------+
Run Code Online (Sandbox Code Playgroud)

可以看出之间不存在一致的赢家SELECT 1,并SELECT *与这两种方法之间的差异可以忽略不计.然而SELECT Not Null col,SELECT PK确实看起来稍快.

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

由于表是空的,因此这种关系似乎只能通过列元数据的数量来解释.因为COUNT(1)很容易看出这会COUNT(*)在下面的过程中的某个时刻被重写.

SET SHOWPLAN_TEXT ON;

GO

SELECT COUNT(1)
FROM master..spt_values
Run Code Online (Sandbox Code Playgroud)

这给出了以下计划

  |--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]))
Run Code Online (Sandbox Code Playgroud)

将调试器附加到SQL Server进程并在执行以下操作时随机中断

DECLARE @V int 

WHILE (1=1)
    SELECT @V=1 WHERE EXISTS (SELECT 1 FROM ##T) OPTION(RECOMPILE)
Run Code Online (Sandbox Code Playgroud)

我发现,在大多数情况下表有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 
Run Code Online (Sandbox Code Playgroud)

此手动分析尝试由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
Run Code Online (Sandbox Code Playgroud)

因此,人们可能会推测,使用时的微小差异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   |
+-------------+-----------+------------+-----------+--------------+
Run Code Online (Sandbox Code Playgroud)

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

  • +1 这个答案值得为获得真实数据所付出的努力获得更多的支持。 (3认同)
  • @MartinBrown - IIRC 最初是 2008 年,尽管我最近在 2012 年重新进行了测试以进行最新的编辑并发现相同。 (3认同)

HLG*_*GEM 8

最好的方法是对两个版本进行性能测试,并查看两个版本的执行计划.选择一个包含大量列的表格.

  • +1.不知道为什么这会被低估.我一直认为教一个人钓鱼比给他一条鱼更好.人们怎么去学习什么? (2认同)

Cad*_*oux 5

SQL Server没有任何区别,它在SQL Server中从来就不是问题.优化器知道它们是相同的.如果你看一下执行计划,你会发现它们是相同的.