联接的虚拟表中的NEWID()导致意外的交叉应用行为


9

我的实际工作查询是一个内部联接,但是这个带有交叉联接的简单示例似乎几乎总是重现了该问题。

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT NEWID() TEST_ID
) BB ( B )

对于我的内部联接,我有很多行,我使用NEWID()函数向每个行添加了一个GUID,对于10行中的大约9行,与2行虚拟表的乘法产生了预期的结果,只有2个副本相同的GUID,而十分之1会产生不同的结果。至少可以说这是出乎意料的,这让我很难在测试数据生成脚本中找到此错误。

如果使用非确定性getdate和sysdatetime函数查看以下查询,您将不会看到此信息,无论如何我都不会看到-我总是在两个最终结果行中看到相同的datetime值。

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT GETDATE() TEST_ID
) BB ( B )

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT SYSDATETIME() TEST_ID
) BB ( B )

我当前正在使用SQL Server 2008,目前的解决方法是在完成随机数据生成脚本之前,将具有GUID的行加载到表变量中。一旦将它们作为表中的值而不是虚拟表中的值,问题就消失了。

我有一个解决方法,但是我正在寻找没有实际表或表变量的解决方法。

在编写此代码时,我尝试了以下几种尝试,但均未成功:1)将newid()放入嵌套的虚拟表中:

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT TEST_ID
    FROM (
        SELECT NEWID() TEST_ID
    ) TT
) BB ( B )

2)将newid()包装在强制转换表达式中,例如:

SELECT CAST(NEWID() AS VARCHAR(100)) TEST_ID

3)反转连接表达式中虚拟表的出现顺序

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS JOIN (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

4)使用不相关的交叉应用

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

在最终发布这个问题之前,现在我似乎成功地尝试了这个,相关的交叉适用:

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT A
    FROM (
        SELECT 1 UNION ALL
        SELECT 2
    ) TT ( A )
    WHERE BB.B IS NOT NULL
) AA ( A )

有人还有其他更优雅,更简单的解决方法吗?如果不需要,我真的不想使用交叉应用或相关性来进行简单的行乘法。

Answers:


20

此行为是设计使然,如此Connect bug报告中详细说明。为方便起见,下面复制了最相关的Microsoft答复(以防该链接在某些时候死掉):

Microsoft在2008年7月7日上午9:27发布

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

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

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

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

因此,我们的建议是:

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

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

GETDATESYSDATETIME功能的确不确定性,但它们被视为运行时常为特定查询。广义上讲,这意味着开始执行查询时将缓存函数的值,并将结果重新用于查询中的所有引用。

问题中的“变通办法”都不是安全的;无法保证下一次编译计划时,下次应用Service Pack或累积更新时行为不会改变...或其他原因。

唯一安全的解决方案是使用某种临时对象-例如变量,表或多语句函数。使用一种基于观察的今天可行的变通办法,是一种很好的方式,可让您将来体验意外的行为,通常以周日上午3点的寻呼警报的形式出现。


“问题中的任何“变通办法”都不是安全的;” 同上。当我尝试将其中之一应用于我的实际工作查询时,它根本没有帮助。
JM Hicks
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.