SQL Server将A <> B拆分为A <B或A> B,如果B是不确定的,则会产生奇怪的结果


26

我们在SQL Server中遇到了一个有趣的问题。考虑以下repro示例:

CREATE TABLE #test (s_guid uniqueidentifier PRIMARY KEY);
INSERT INTO #test (s_guid) VALUES ('7E28EFF8-A80A-45E4-BFE0-C13989D69618');

SELECT s_guid FROM #test
WHERE s_guid = '7E28EFF8-A80A-45E4-BFE0-C13989D69618'
  AND s_guid <> NEWID();

DROP TABLE #test;

小提琴

请暂时忘记这种s_guid <> NEWID()情况似乎毫无用处-这只是一个最小的复制示例。由于NEWID()匹配某个给定常数的可能性非常小,因此每次都应将其评估为TRUE。

但事实并非如此。运行此查询通常返回1行,但有时(非常频繁,在10中超过1次)返回0行。我已经在系统上使用SQL Server 2008对其进行了复制,您可以使用上面链接的小提琴(SQL Server 2014)在线对其进行复制。

查看执行计划可以发现查询分析器显然将条件分为s_guid < NEWID() OR s_guid > NEWID()

查询计划屏幕截图

...这完全解释了为什么有时会失败(如果第一个生成的ID小于给定ID,而第二个ID大于给定ID)。

即使其中一个表达式不是确定性的,也允许SQL Server将其评估A <> BA < B OR A > B,吗?如果是,请在哪里记录?还是我们发现了错误?

有趣的是,AND NOT (s_guid = NEWID())产生相同的执行计划(和相同的随机结果)。

当开发人员想要有选择地排除特定行并使用以下代码时,我们发现了此问题:

s_guid <> ISNULL(@someParameter, NEWID())

作为以下内容的“快捷方式”:

(@someParameter IS NULL OR s_guid <> @someParameter)

我正在寻找文档和/或错误确认。该代码并不十分相关,因此不需要解决方法。


Answers:


22

即使其中一个表达式不是确定性的,也允许SQL Server将其评估A <> BA < B OR A > B,吗?

这是一个有争议的观点,答案是合格的“是”。

我了解到的最好的讨论是对Itzik Ben-Gan的Connect bug报告(带有NEWID和Table Expressions的Bug)的回答,该问题由于无法解决而被关闭。自此以来,Connect已停用,因此该链接指向Web存档。可悲的是,Connect的灭亡使许多有用的材料丢失了(或变得更难找到)。无论如何,Microsoft的Jim Hogg最有用的报价是:

这触及了问题的核心-是否允许通过优化来更改程序的语义?即:如果某个程序产生某些答案,但运行缓慢,那么Query Optimizer是否合法使该程序运行更快,但还会更改给出的结果?

在喊“不!”之前 (我自己也很喜欢:-),考虑一下:好消息是,在99%的情况下,答案是相同的。因此,查询优化是一个明显的胜利。坏消息是,如果查询包含副作用代码,则不同的计划确实会产生不同的结果。而NEWID()就是这种副作用(不确定性)“功能”之一,它揭示了差异。[实际上,如果您进行实验,则可以设计其他方法-例如,对AND子句进行短路求值:使第二个子句抛出算术除数为零-在第一个子句之前可以执行第二个子句的不同的优化方式]在该线程的其他地方,Craig的解释是SqlServer不保证何时执行标量运算符。

因此,我们有一个选择:如果我们想在存在不确定性(副作用)代码的情况下保证某种行为-例如,JOIN的结果遵循嵌套循环执行的语义-那么我们可以使用适当的选项来强制执行该行为-UC指出。但是生成的代码将运行缓慢-实际上,这是使查询优化器陷入困境的代价。

综上所述,我们正在朝着NEWID()的“预期”行为的方向移动查询优化器-权衡“预期结果”的性能。

在这方面行为随时间变化的一个示例是NULLIF与诸如RAND()之类的不确定函数无法正确工作。还有其他类似的情况,例如COALESCE,使用带有子查询的子查询可能会产生意外的结果,并且这种情况也正在逐步解决。

吉姆继续说:

结束循环。。。我已经与开发团队讨论了这个问题。最终,由于以下原因,我们决定不更改当前行为:

1)优化器不保证标量函数的执行时间或执行次数。这是一个长期存在的宗旨。这是基本的“余地”,它使优化程序有足够的自由来获得查询计划执行的显着改进。

2)尽管没有广泛讨论,但这种“每行行为”并不是一个新问题。在Yukon版本中,我们开始调整其行为。但是,在所有情况下都很难精确确定其含义!例如,它是否适用于在最终结果“途中”计算的中间行?-在这种情况下,它显然取决于所选的计划。还是仅适用于最终将出现在完成结果中的行?-这里有一个令人讨厌的递归,因为我敢肯定你会同意的!

3)正如我之前提到的,我们默认为“优化性能”-这对于99%的情况而言是好的。可能会改变结果的1%的情况相当容易发现-具有副作用的“功能”(例如NEWID),也很容易“修复”(因此,交易性能)。长期以来,这种默认设置是“优化性能”,并且已被接受。(是的,这不是编译器为常规编程语言选择的立场,而是那样)。

因此,我们的建议是:

a)避免依赖非保证的时间安排和执行次数语义。b)避免在表表达式中深入使用NEWID()。c)使用OPTION强制执行特定行为(交易性能)

希望此解释有助于阐明我们将此错误关闭为“无法修复”的原因。


有趣的是,AND NOT (s_guid = NEWID())产生相同的执行计划

这是归一化的结果,归一化在查询编译的早期就发生了。这两个表达式都编译为完全相同的规范化形式,因此产生了相同的执行计划。


在这种情况下,如果我们想强制执行一个似乎可以避免该问题的特定计划,则可以使用WITH(FORCESCAN)。可以肯定的是,在执行查询之前,我们应该使用变量存储NEWID()的结果。
拉兹万·索科尔

11

在此处记录(某种)记录:

在查询中指定的函数实际执行的次数在优化器建立的执行计划之间可能会有所不同。一个示例是由WHERE子句中的子查询调用的函数。子查询及其功能的执行次数可能会因优化程序选择的访问路径不同而异。

用户定义的功能

这不是查询计划将多次执行NEWID()并更改结果的唯一查询形式。这是令人困惑的,但实际上对于NEWID()对于密钥生成和随机排序很有用。

最令人困惑的是,并非所有非确定性函数实际上都像这样。例如,RAND()和GETDATE()将对每个查询仅执行一次。


是否有任何博客文章或类似文章解释为什么/何时引擎将“不等于”转换为范围?
Magoo先生

3
从来没听说过。可能是例行的,因为=<>可以针对BTree有效地进行评估。
David Browne-微软

5

值得一看的是,如果您看一下这份旧的SQL 92标准文档,则有关“不等式”的要求在“ 8.2 <comparison predicate>” 部分中描述如下:

1)令X和Y为任意两个对应的<行值构造器元素>。令XV和YV分别为X和Y表示的值。

[...]

ii)当且仅当XV和YV不相等时,“ X <> Y”为真。

[...]

7)假设Rx和Ry分别是<comparison谓词>的两个<行值构造器>,而RXi和RYi分别是Rx和Ry的第i个<行值构造器元素>。“ Rx <comp op> Ry”为真,假或未知,如下所示:

[...]

b)当且仅当对于某些i的RXi <> RYi,“ x <> Ry”为真。

[...]

h)仅当“ Rx = Ry”为真时,“ x <> Ry”为假。

注意:为了完整起见,我将7b和7h包括在内是因为它们谈论<>比较-我认为T-SQL不会对具有多个值的行值构造函数进行比较,除非我只是完全误解了它的含义-这很可能

这是一堆令人困惑的垃圾。但是如果你想让垃圾箱保持潜水状态...

认为 1.ii是适用于这种情况的项目,因为我们正在比较“行值构造器元素”的值。

ii)当且仅当XV和YV不相等时,“ X <> Y”为真。

基本上,X <> Y如果X和Y表示的不相等,这是正确的。由于X < Y OR X > Y该谓词在逻辑上是等效的重写,因此优化程序使用该谓词完全很酷。

该标准没有对与<>比较运算符两侧的行值构造函数元素的确定性(或任何得到的结果)相关的定义施加任何约束。用户代码的责任是处理以下事实:一侧的值表达式可能不确定。


1
我会拒绝投票(赞成或反对),但我不相信。您提供的报价提及“值”。我的理解是,比较是在两个值之间进行的,每一边一个。不在值的两个(或更多)实例化之间。另外,标准(至少您引用的是92)在所有非确定性函数中均未提及。通过与您类似的推理,我们可以假定符合标准的SQL产品不提供任何不确定性功能,而仅提供标准中提到的功能。
ypercubeᵀᴹ

@yper感谢您的反馈!我认为您的解释绝对正确。这是我第一次阅读该文档。它是在“行值构造函数”所代表的值的上下文中提及值,在文档的其他地方它可以说是标量子查询(在许多其他事物中)。标量子查询尤其看起来似乎是不确定的。但我真的不知道我在说什么=)
Josh Darnell
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.