这是一个奇怪的问题,但我对这种行为的解释有点沮丧:
背景:(不需要知道)
首先,我正在编写一个快速查询并粘贴UNIQUERIDENTIFIER
列表,并希望它们在WHERE X IN (...)
子句内部是统一的。过去,我在列表顶部使用了一个空的UNIQUERIDENTIFIER
(全零),这样我就可以粘贴一组UNIQUERIDENTIFIER
的统一集:,'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
。这一次,为了避免点击零,我插入一个NEWID()
,认为碰撞的几率几乎是不可能的,令我惊讶的是,产生了数千个额外的结果,比如50%的表格。
开始提问:(您需要知道的部分)
此查询:
-- SETUP: (i boiled this down to the bare minimum)
-- just creating a table with 500 PK UNIQUERIDENTIFIERs
IF (OBJECT_ID('tempdb..#wtfTable') IS NOT NULL) DROP TABLE #wtfTable;
CREATE TABLE #wtfTable (WtfId UNIQUEIDENTIFIER PRIMARY KEY);
INSERT INTO #wtfTable
SELECT TOP(500) NEWID()
FROM master.sys.all_objects o1 (NOLOCK)
CROSS JOIN master.sys.all_objects o2 (NOLOCK);
-- ACTUAL QUERY:
SELECT *
FROM #wtfTable
WHERE [WtfId] IN ('00000000-0000-0000-0000-000000000000', NEWID());
......应该统计产生bupkis。但如果你运行十次左右,你有时会得到大量的选择。例如,在最后一次运行中,我收到了465/500行,这意味着超过93%的行被返回。
虽然我理解NEWID()
将在每行的基础上重新计算,但是在地狱中没有统计数据可以达到那么多。我在这里写的所有东西都需要产生细致入微的SELECT
,删除任何东西都会阻止它发生。顺便提一下,您可以将IN
替换为WHERE WtfId = '...' OR WtfId = NEWID()
,但仍会收到相同的结果。我正在使用SQL SERVER 2014 Standard补丁到目前为止,没有激活奇怪的设置,我知道。
所以那里的任何人都知道这是怎么回事?提前致谢。
编辑:
'00000000-0000-0000-0000-000000000000'
是一个红色的鲱鱼,这是一个使用整数的版本:(有趣的是,我需要用整数将表大小提高到1000以产生有问题的查询计划......)
IF (OBJECT_ID('tempdb..#wtfTable') IS NOT NULL) DROP TABLE #wtfTable;
CREATE TABLE #wtfTable (WtfId INT PRIMARY KEY);
INSERT INTO #wtfTable
SELECT DISTINCT TOP(1000) CAST(CAST('0x' + LEFT(NEWID(), 8) AS VARBINARY) AS INT)
FROM sys.tables o1 (NOLOCK)
CROSS JOIN sys.tables o2 (NOLOCK);
SELECT *
FROM #wtfTable
WHERE [WtfId] IN (0, CAST(CAST('0x' + LEFT(NEWID(), 8) AS VARBINARY) AS INT));
或者你可以只替换文字UNIQUEIDENTIFIER
并执行此操作:
DECLARE @someId UNIQUEIDENTIFIER = NEWID();
SELECT *
FROM #wtfTable
WHERE [WtfId] IN (@someId, NEWID());
两者产生相同的结果......问题是为什么会发生这种情况?
答案 0 :(得分:9)
让我们看一下执行计划。
在此查询的特定运行中,Seek
返回51行而不是估计1行。
以下实际查询会生成具有相同形状的计划,但更容易对其进行分析,因为我们有两个变量@ID1
和@ID2
,您可以跟踪计划。
CREATE TABLE #wtfTable (WtfId UNIQUEIDENTIFIER PRIMARY KEY);
INSERT INTO #wtfTable
SELECT TOP(500) NEWID()
FROM master.sys.all_objects o1 (NOLOCK)
CROSS JOIN master.sys.all_objects o2 (NOLOCK);
DECLARE @ID1 UNIQUEIDENTIFIER;
DECLARE @ID2 UNIQUEIDENTIFIER;
SELECT TOP(1) @ID1 = WtfId
FROM #wtfTable
ORDER BY WtfId;
SELECT TOP(1) @ID2 = WtfId
FROM #wtfTable
ORDER BY WtfId DESC;
-- ACTUAL QUERY:
SELECT *
FROM #wtfTable
WHERE WtfId IN (@ID1, @ID2);
DROP TABLE #wtfTable;
如果仔细检查此计划中的运算符,您会看到IN
部分查询被转换为包含两行三列的表。 Concatenation
运算符返回此表。此帮助程序表中的每一行都定义了索引中的搜索范围。
ExpFrom ExpTo ExpFlags
@ID1 @ID1 62
@ID2 @ID2 62
内部ExpFlags
指定需要哪种范围搜索(<
,<=
,>
,>=
)。如果您向IN
子句添加更多变量,您将在连接到此帮助程序表的计划中看到它们。
Sort
和Merge Interval
运算符确保合并任何可能的重叠范围。查看Merge Interval
operator详细信息Fabiano Amorim,其中检查了具有此形状的计划。 Here is another good post关于保罗怀特的这个计划形式。
最后,带有两行的辅助表与主表连接,对于辅助表中的每一行,在ExpFrom
到ExpTo
的聚簇索引中有一个范围搜索,这是显示在Index Seek
运算符中。 Seek
运算符显示<
和>
,但这会产生误导。实际比较由Flags
值在内部定义。
如果您有一组不同的范围,例如:
WHERE
([WtfId] >= @ID1 AND [WtfId] < @ID2)
OR [WtfId] = @ID3
,您仍会看到具有相同搜索谓词的计划的相同形状,但不同的Flags
值。
所以,有两个寻求:
from @ID1 to @ID1, which returns one row
from @ID2 to @ID2, which returns one row
在带有变量的查询中,内部表达式会导致在需要时从变量中获取值。在查询执行期间,变量的值不会发生变化,并且所有内容都按预期正常运行。
NEWID()
如何影响 我们在您的示例中使用NEWID
:
SELECT *
FROM #wtfTable
WHERE WtfId IN ('00000000-0000-0000-0000-000000000000', NEWID());
计划和所有内部处理与变量相同。
不同之处在于此内部表有效地变为:
ExpFrom ExpTo ExpFlags
0...0 0...0 62
NEWID() NEWID() 62
NEWID()
被称为两次次。当然,每次调用都会产生一个不同的值,偶然会产生一个覆盖表中某些现有值的范围。
聚集索引有两个范围扫描,范围为
from `0...0` to `0...0`
from `some_id_1` to `some_id_2`
现在很容易看到这样的查询如何返回某些行,即使NEWID
碰撞的可能性非常小。
显然,优化者认为它可以调用NEWID
两次而不是记住第一个生成的随机值并在查询中进一步使用它。还有其他一些情况,优化者称NEWID
次数超过预期产生类似看似不可能的结果。
例如:
Is it legal for SQL Server to fill PERSISTED columns with data that does not match the definition?
Inconsistent results with NEWID() and PERSISTED computed column
优化工具应该知道NEWID()
是非确定性的。总的来说,这感觉就像一个错误。
我对SQL Server内部结构一无所知,但我的猜测看起来像这样:有RAND()
这样的运行时常量函数。 NEWID()
被错误地归入此类别。然后有人注意到人们不希望它以同样的方式返回相同的ID,因为RAND()
为每次调用返回相同的随机数。他们每次NEWID()
出现在表达式中时,通过实际重新生成新ID来修补它。但是优化器的总体规则与RAND()
保持一致,因此更高级别的优化器认为NEWID()
的所有调用都返回相同的值,并使用NEWID()
自由重新排列表达式,从而导致意外结果。
还有一个关于NEWID()
的类似奇怪行为的问题:
NEWID() In Joined Virtual Table Causes Unintended Cross Apply Behavior
答案是有一个Connect bug report,它已关闭,因为&#34;无法修复&#34;。微软的评论基本上说这种行为是设计的。
优化器不保证执行的时间或次数 标量函数。这是一个长期建立的宗旨。它是 基本的&#39;余地&#39; tha允许优化器获得足够的自由 查询计划执行方面的重大改进。
答案 1 :(得分:1)
以下查询按预期返回任何内容 内部类型转换会导致意外结果,我想
SELECT *
FROM wtfTable
WHERE convert(varchar(100),WtfId) = '00000000-0000-0000-0000-000000000000'
or WtfId = NEWID() ;