Rys*_*ian 39 sql-server-2008 sql-server-2012
下面是一个简单的例子,它返回奇怪的结果,这是不可预测的,我们无法在我们的团队中解释。我们做错了什么还是 SQL Server 错误?
经过一番调查,我们将搜索区域缩小到子查询中的 union 子句,从“men”表中选择一条记录
它在 SQL Server 2000 中按预期工作(返回 12 行),但在 2008 和 2012 年它只返回一行。
create table dual (dummy int)
insert into dual values (0)
create table men (
man_id int,
wife_id int )
-- there are 12 men, 6 married
insert into men values (1, 1)
insert into men values (2, 2)
insert into men values (3, null)
insert into men values (4, null)
insert into men values (5, null)
insert into men values (6, 3)
insert into men values (7, 5)
insert into men values (8, 7)
insert into men values (9, null)
insert into men values (10, null)
insert into men values (11, null)
insert into men values (12, 9)
Run Code Online (Sandbox Code Playgroud)
这仅返回一行:1 1 2
select
man_id,
wife_id,
(select count( * ) from
(select dummy from dual
union select men.wife_id ) family_members
) as family_size
from men
--where wife_id = 2 -- uncomment me and try again
Run Code Online (Sandbox Code Playgroud)
取消注释最后一行,它给出:2 2 2
有很多奇怪的行为:
Pau*_*ite 38
我们做错了什么还是 SQL Server 错误?
这是一个错误结果的错误,您应该通过您常用的支持渠道报告。如果您没有支持协议,了解付费事件通常会在 Microsoft 确认该行为是错误的情况下退款可能会有所帮助。
该错误需要三个成分:
例如,问题中的查询生成如下计划:

有很多方法可以删除这些元素之一,因此该错误不再重现。
例如,可以创建索引或统计信息,这意味着优化器选择不使用惰性索引假脱机。或者,可以使用提示来强制散列或合并联合,而不是使用串联。还可以重写查询以表达相同的语义,但这会导致不同的计划形状,其中缺少一个或多个必需元素。
延迟索引假脱机在由外部引用(相关参数)值索引的工作表中延迟缓存内部结果行。如果 Lazy Index Spool 被要求提供它之前见过的外部引用,它会从其工作表中获取缓存的结果行(“倒带”)。如果 spool 被要求提供一个它以前从未见过的外部参考值,它会使用当前外部参考值运行其子树并缓存结果(“重新绑定”)。Lazy Index Spool 上的搜索谓词指示其工作表的键。
当线轴检查新的外部参考是否与之前看到的相同时,此特定平面形状中会出现问题。Nested Loops Join 正确更新其外部引用,并通过其PrepRecompute接口方法通知操作符内部输入。在此检查开始时,内部操作员读取CParamBounds:FNeedToReload属性以查看外部引用是否比上次更改。示例堆栈跟踪如下所示:

当上面显示的子树存在时,特别是在使用串联的地方,绑定会出现问题(可能是 ByVal/ByRef/Copy 问题)CParamBounds:FNeedToReload,无论外部引用是否实际更改,总是返回 false。
当存在相同的子树,但使用合并联合或散列联合时,在每次迭代时正确设置此基本属性,并且 Lazy Index Spool 每次都根据需要倒带或重新绑定。顺便说一下,Distinct Sort 和 Stream Aggregate 是无可指责的。我怀疑 Merge 和 Hash Union 会复制以前的值,而 Concatenation 使用引用。不幸的是,如果不访问 SQL Server 源代码,几乎不可能验证这一点。
最终结果是有问题的计划形状中的 Lazy Index Spool 总是认为它已经看到了当前的外部引用,通过查找到它的工作表来回退,通常什么也没找到,所以没有为该外部引用返回任何行。在调试器中逐步执行,假脱机只执行它的RewindHelper方法,而不是它的ReloadHelper方法(在这种情况下 reload = rebind)。这在执行计划中很明显,因为 spool 下的操作符都有 'Number of Executions = 1'。

当然,例外情况是第一个外部引用给出了惰性索引假脱机。这总是执行子树并在工作表中缓存结果行。所有后续迭代都会导致倒带,仅当当前迭代具有与第一次相同的外部引用值时,才会生成一行(单个缓存行)。
因此,对于嵌套循环连接外侧的任何给定输入集,查询将返回与处理的第一行的重复项一样多的行(当然,加上第一行本身的一个)。
表和样本数据:
CREATE TABLE #T1
(
pk integer IDENTITY NOT NULL,
c1 integer NOT NULL,
CONSTRAINT PK_T1
PRIMARY KEY CLUSTERED (pk)
);
GO
INSERT #T1 (c1)
VALUES
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6);
Run Code Online (Sandbox Code Playgroud)
以下(简单的)查询使用合并联合为每行(总共 18 个)生成正确的 2 计数:
SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY
(
SELECT COUNT_BIG(*) AS c1
FROM
(
SELECT T1.c1
UNION
SELECT NULL
) AS U
) AS C;
Run Code Online (Sandbox Code Playgroud)

如果我们现在添加一个查询提示来强制连接:
SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY
(
SELECT COUNT_BIG(*) AS c1
FROM
(
SELECT T1.c1
UNION
SELECT NULL
) AS U
) AS C
OPTION (CONCAT UNION);
Run Code Online (Sandbox Code Playgroud)
执行计划的形状有问题:

结果现在不正确,只有三行:

虽然不能保证这种行为,但聚集索引扫描的第一行的c1值为 1。还有其他两行具有此值,因此总共生成了三行。
现在截断数据表并使用“第一”行的更多重复项加载它:
TRUNCATE TABLE #T1;
INSERT #T1 (c1)
VALUES
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6),
(1), (1), (1), (1), (1), (1);
Run Code Online (Sandbox Code Playgroud)
现在串联计划是:

并且,如所示,生成了 8 行c1 = 1,当然所有行:

我注意到您为此错误打开了一个连接项目,但实际上这不是报告对生产产生影响的问题的地方。如果是这种情况,您真的应该联系 Microsoft 支持。
这个错误结果的错误在某个阶段得到了修复。从 2012 年开始,它不再在任何版本的 SQL Server 上为我重现。它在 SQL Server 2008 R2 SP3-GDR build 10.50.6560.0 (X64) 上重现。
小智 -3
为什么使用子查询而不使用 from 语句?我认为这可能会导致2005年和2008年服务器的差异。也许你可以使用显式连接?
select
m1.man_id,
m1.wife_id,
(select count( * ) from
(select dummy from dual
union
select m2.wife_id
from men m2
where m2.man_id = m1.man_id) family_members
) as family_size
from men m1
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2638 次 |
| 最近记录: |