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

Sql 使用Exists 1或Exists的子查询*,sql,sql-server,tsql,Sql,Sql Server,Tsql,我过去经常这样写我的支票: 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 * FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Where Columns=@Filters
END
前世的一位DBA告诉我,当我执行EXISTS子句时,请使用SELECT 1而不是SELECT*


这真的有区别吗?

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

没有任何实际区别,但可能会对性能造成很小的影响。根据经验,你不应该要求比你需要的更多的数据。

就我个人而言,很难相信他们没有针对相同的查询计划进行优化。但在你的特殊情况下,唯一知道的方法就是测试它。如果有,请回来报到

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

引用微软的话:

子查询的选择列表 由引入的几乎总是存在的 由星号*组成。有 没有理由列出列名,因为 您只是在测试 满足合同中规定的条件 子查询存在

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

SELECT whatever
  FROM yourtable
 WHERE EXISTS( SELECT 1/0
                 FROM someothertable 
                WHERE a_valid_clause )
如果它真的在用SELECT列表做一些事情,它会抛出一个div by zero错误。没有

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

ANSI SQL 1992标准,第191页

3例: a如果*仅包含在a中 立即包含在中,则 相当于 这是一种武断的态度


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

产生这种误解的原因大概是因为相信它最终会阅读所有的专栏。不难看出事实并非如此

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解释说,在这种情况下,他通常使用SELECT 1,因为它会在查询编译过程中产生微小的性能差异

QP将采用并扩展所有* 并将其绑定到 对象在本例中为 柱。然后它将被移除 由于 查询

所以对于一个简单的EXISTS子查询,比如 这:

从存在的MyTable中选择col1从MyTable中选择*。col1=Table2.col2*将被 扩展到一些潜在的大公司 列列表,然后它将 确定 EXISTS不需要任何这些 列,所以基本上所有列都可以 被移除

选择1将避免 为此检查任何不需要的元数据 查询编译期间的表

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

我测试了在具有不同列数的空表上表达此查询的四种可能方式。选择1 vs选择*vs选择主键vs选择其他非空列

我使用optionrecompile在一个循环中运行查询,并测量每秒的平均执行次数。结果如下

+-------------+----------+---------+---------+--------------+
| 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的显示速度稍快

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

由于表是空的,因此这种关系似乎只能通过列元数据的数量来解释。对于COUNT1,很容易看出,在下面的过程中,它在某个时候被重写为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)
    
我发现,在表中有1024列的情况下,大多数时候调用堆栈看起来像下面这样,这表明它确实花费了很大一部分时间加载列元数据,即使选择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代码分析器支持此手动评测尝试,它显示了非常不同的函数选择,这两种情况下VS消耗了编译时间

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

我从一次谈话中抄袭了一个例子

所以 有人可能会猜测,当使用selectsomenotnull列时,一个细微的明显区别是,它只会检查特定列的权限,但仍然会加载所有列的元数据。然而,这似乎与事实不符,因为两种方法之间的百分比差异随着基础表中列数的增加而减小

在任何情况下,我都不会匆忙地将所有查询更改为这种形式,因为差异非常小,而且只在查询编译期间才明显。删除“重新编译”选项,以便后续执行可以使用缓存的计划,会产生以下结果

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

您忘记存在从…中选择空值。。。。这是最近btwp.s提出的问题。找一个新的DBA。迷信在其中没有一席之地,尤其是在以前的DBA的数据库管理中+1.不知道为什么会被否决。我一直认为教一个人钓鱼比给他一条鱼要好。人们将如何学习任何东西?+1对于获取真实数据的努力,这个答案值得更多的投票。知道这些统计数据是在哪个版本的SQL Server上生成的吗?@MartinBrown-IIRC最初是在2008年,尽管我最近在2012年对测试进行了重新编辑,发现了相同的结果。1/0的存在技巧甚至可以是相同的扩展到此选择1,其中存在选择1/0。。。因为第二个选择没有FROM,所以看起来更抽象了一步clause@whytheq-或选择存在的计数*选择1/0。SQL Server中不带FROM的SELECT将被视为访问单行表,例如,类似于其他SQL Server中的双表选择RDBMSs@MartinSmith干杯-重点是SELECT在执行任何其他操作之前创建了一个1行表,所以即使1/0是垃圾,1行表仍然存在?一直都是这样,或者它是SQL Server特定版本中引入的优化?@MartinSmith TIL quot。谢谢你把它修好。
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
+-------------+-----------+------------+-----------+--------------+
| 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   |
+-------------+-----------+------------+-----------+--------------+