JM *_*cks 10 sql-server-2008 sql-server database-internals
我的实际工作查询是内部联接,但是这个带有交叉联接的简单示例似乎几乎总是会重现该问题。
SELECT *
FROM (
SELECT 1 UNION ALL
SELECT 2
) AA ( A )
CROSS JOIN (
SELECT NEWID() TEST_ID
) BB ( B )
Run Code Online (Sandbox Code Playgroud)
通过我的内部连接,我有很多行,我使用 NEWID() 函数为每个行添加了一个 GUID,对于大约 10 个这样的行中的 9 个,与 2 行虚拟表的乘法产生了预期的结果,只有 2 个副本相同的 GUID,而十分之一会产生不同的结果。至少可以说这是出乎意料的,让我很难在我的测试数据生成脚本中找到这个错误。
如果您使用非确定性 getdate 和 sysdatetime 函数查看以下查询,您将看不到这一点,无论如何我都看不到 - 我总是在两个最终结果行中看到相同的日期时间值。
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 )
Run Code Online (Sandbox Code Playgroud)
我目前正在使用 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 )
Run Code Online (Sandbox Code Playgroud)
2) 将 newid() 包装在一个强制转换表达式中,例如:
SELECT CAST(NEWID() AS VARCHAR(100)) TEST_ID
Run Code Online (Sandbox Code Playgroud)
3) 颠倒连接表达式中虚拟表的出现顺序
SELECT *
FROM (
SELECT NEWID() TEST_ID
) BB ( B )
CROSS JOIN (
SELECT 1 UNION ALL
SELECT 2
) AA ( A )
Run Code Online (Sandbox Code Playgroud)
4)使用不相关的交叉应用
SELECT *
FROM (
SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
SELECT 1 UNION ALL
SELECT 2
) AA ( A )
Run Code Online (Sandbox Code Playgroud)
就在最后发布这个问题之前,现在我似乎成功地尝试了这个,一个相关的交叉应用:
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 )
Run Code Online (Sandbox Code Playgroud)
任何人都有其他更优雅,更简单的解决方法?如果不需要,我真的不想对简单的行乘法使用交叉应用或相关。
Pau*_*ite 20
此行为是设计使然,正如在此 Connect 错误报告中详细解释的那样。为方便起见,下面复制了最相关的 Microsoft 回复(以防链接在某些时候失效):
Microsoft 于 2008 年 7 月 7 日上午 9:27 发布
关闭循环。. . 我已经与开发团队讨论过这个问题。最终我们决定不改变当前的行为,原因如下:
优化器不保证标量函数的执行时间或执行次数。这是一个由来已久的信条。这是基本的“余地”,它允许优化器有足够的自由度来获得查询计划执行的显着改进。
这种“每行一次的行为”并不是一个新问题,尽管它没有被广泛讨论。我们开始在 Yukon 版本中调整它的行为。但是在所有情况下都很难准确确定它的确切含义!例如,它是否适用于“在途中”计算到最终结果的中间行?- 在这种情况下,这显然取决于所选择的计划。还是只适用于最终会出现在完成结果中的行?- 这里发生了令人讨厌的递归,我相信你会同意的!
正如我之前提到的,我们默认为“优化性能”——这对 99% 的情况都有好处。可能会改变结果的 1% 的情况很容易被发现 - 副作用“功能”,例如 NEWID - 并且很容易“修复”(因此,交易性能)。再次“优化性能”的默认设置是长期存在的,并被接受。(是的,这不是编译器为传统编程语言选择的立场,但就这样吧)。
所以,我们的建议是:
- 避免依赖非保证时间和执行次数语义。
- 避免在表表达式中使用 NEWID()。
- 使用 OPTION 强制特定行为(交易性能)
希望这个解释有助于澄清我们将这个错误关闭为“不会修复”的原因。
该GETDATE
和SYSDATETIME
功能的确不确定性,但它们被视为运行时常为特定查询。从广义上讲,这意味着函数的值在查询执行开始时被缓存,并且结果重新用于查询中的所有引用。
问题中的“解决方法”都不是安全的;无法保证在下次编译计划、下次应用服务包或累积更新时……或出于其他原因,行为不会改变。
唯一安全的解决方案是使用某种临时对象 - 例如变量、表或多语句函数。根据观察,使用今天似乎有效的解决方法是在未来体验意外行为的好方法,通常以周日凌晨 3 点的寻呼警报的形式出现。
归档时间: |
|
查看次数: |
1658 次 |
最近记录: |