可以肯定,这是一个奇怪的问题,但我对这种行为的解释有点困惑:
背景:(不需要知道)
所以一开始,我正在编写一个快速查询并粘贴一个 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 标准,我知道没有激活任何奇怪的设置。那么有人知道这是怎么回事吗?提前致谢。
编辑:
'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());
两者都产生相同的结果......问题是 为什么会发生这种情况?
最佳答案
让我们看一下执行计划。
在此特定的查询运行中,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的详细文章,该文章检查了具有这种形状的计划。 Paul White关于这个计划形状的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
冲突的机会很小。显然,optimiser认为它可以调用
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()
被错误地归为此类。然后有人注意到,人们不希望它以与RAND()
为每次调用返回相同的随机数相同的方式返回相同的ID。并且,他们每次在表达式中出现NEWID()
时,都会通过实际上重新生成新的ID对其进行修补。但是,优化程序的总体规则与RAND()
相同,因此更高级别的优化程序认为NEWID()
的所有调用都返回相同的值,并使用NEWID()
自由地重新排列表达式,从而导致意外结果。还有另一个关于
NEWID()
的类似奇怪行为的问题:NEWID() In Joined Virtual Table Causes Unintended Cross Apply Behavior
答案是说有一个Connect bug report,它被关闭为“Wo n't fix”。 Microsoft的评论实质上说这种行为是设计使然。
关于sql - "WHERE"子句中带有 NEWID() 的奇数 SQL Server (TSQL) 查询结果,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38498513/