在MS SQL Server 2012中运行以下查询时,第二个查询失败,但第一个查询失败。同样,如果在没有where子句的情况下运行,则两个查询都会失败。我很茫然,为什么两个都失败,因为两个都应该有空结果集。任何帮助/见解表示赞赏。

create table #temp
(id     int primary key)

create table #temp2
(id     int)

select 1/0
from #temp
where id = 1

select 1/0
from #temp2
where id = 1


#1 楼

初步查看执行计划后,发现表达式1/0是在Compute Scalar运算符中定义的:

现在,即使执行计划确实从最左侧开始执行,也要迭代调用OpenGetRow方法子迭代器返回结果,SQL Server 2005和更高版本包含一种优化,该表达式通常仅由Compute Scalar定义表达式,并将评估推迟到随后的操作需要结果时:

在这种情况下,仅当组装要返回给客户的行时才需要表达式结果(您可以认为这发生在绿色的SELECT图标上)。按照这种逻辑,延迟求值将意味着永远不会对表达式求值,因为两个计划都不会生成返回行。稍微花点时间,聚簇索引搜索和表扫描都不会返回一行,因此没有要汇编的行可以返回给客户端。
但是,有一个单独的优化可以识别一些表达式作为运行时常量,因此在查询执行开始之前进行一次评估。
在这种情况下*,可以在showplan XML(左侧的“聚集索引搜索”计划,右侧的“表扫描”计划)中找到已发生的指示:

在此博客文章中,我写了更多有关底层机制以及它们如何影响性能的内容。使用那里提供的信息,我们可以修改第一个查询,以便在执行开始之前对两个表达式进行求值和缓存:
select 1/0 * CONVERT(integer, @@DBTS)
from #temp
where id = 1

select 1/0
from #temp2
where id = 1

现在,第一个计划还包含一个常量表达式引用,并且两个查询都产生错误消息。第一个查询的XML包含:

更多信息:计算标量,表达式和性能

由于运行时常量缓存而报告错误的示例,但存在执行计划中没有说明(图形或XML):
SELECT TOP (1) id
FROM #temp2
WHERE id = 1
ORDER BY 1/0;



#2 楼

我将明智地猜测(并且在此过程中可能会吸引SQL Server专家,他可能会给出非常详细的答案)。

第一个查询将执行方式如下:


扫描主键索引
在查询中查找数据表中的值

之所以选择此路径,是因为您在主键上有一个where子句。它永远不会进入第二步,因此查询不会失败。

第二步没有主键可以运行,因此它的查询方式为:


对数据进行全表扫描并检索必要的值

其中一个值是引起问题的1/0

这是SQL的示例服务器优化查询。在大多数情况下,这是一件好事。 SQL Server会将条件从select移到表扫描操作中。这通常可以节省查询评估的步骤。

但是,这种优化并不是一件好事。实际上,这似乎违反了SQL Server文档本身,该文档指出where子句先于select进行求值。好吧,他们可能对此有一些博学的解释。但是,对于大多数人而言,在where之前进行逻辑处理select意味着(除其他事项外)“不会在未返回用户的行上生成select-子句错误”。

评论


如果您是对的话,+ 1毫无头绪,但是鉴于唯一的区别是主键,我可以看到最佳答案。

–第十二
14年2月13日在18:49

@GordonLinoff保罗·兰德尔(Paul Randal)刚刚在Twitter上确认您的回覆不胜枚举。

–SchmitzIT
2014年2月13日在19:12

@Still,实际的执行顺序(无论如何)不应导致类似的错误消息。

–超立方体ᵀᴹ
2014年2月13日21:00

@ypercube Erland Sommarskog会同意你的观点(连接项)

–保罗·怀特♦
2014年2月13日在21:04

谢谢您的指点-我已登录并支持该请求。

–戈登·利诺夫(Gordon Linoff)
14年2月13日在21:08