经过一些调查后,我们将搜索区域缩减为子查询中的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)
这只返回一行: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
取消注释最后一行,它给出:2 2 2
有很多奇怪的行为:
>经过一系列的下降,创建,截断和插入“men”表后,它有时会起作用(返回12行)
>当你将“union select men.wife_id”更改为“union all select men.wife_id”或“union select isnull(men.wife_id,null)”(!!!)时,它会返回12行(如预期的那样).
>奇怪的行为似乎与列“wife_id”的数据类型无关.我们在开发系统上观察到它有更多的数据集.
>“其中wife_id> 0”返回6行
>我们还通过这种陈述观察到奇怪的观点行为. SELECT *返回行的子集,SELECT TOP 1000返回all
解决方法
Are we doing something wrong or is it sql Server error?
这是一个错误结果错误,您应该通过常用支持渠道报告.如果您没有支持协议,如果Microsoft确认该行为是错误,则可能有助于知道paid incidents通常会退款.
这个bug需要三个成分:
>具有外部引用的嵌套循环(应用)
>一个内侧惰性索引线轴,用于寻找外部参考
>内部连接运算符
例如,可以创建索引或统计信息,这恰好意味着优化器选择不使用惰性索引线程.或者,可以使用提示来强制哈希或合并联合而不是使用连接.还可以重写查询以表达相同的语义,但这会导致不同的计划形状,其中缺少一个或多个所需元素.
更多细节
惰性索引假脱机在由外部引用(相关参数)值索引的工作表中懒惰地缓存内侧结果行.如果要求Lazy Index Spool提供之前看到的外部引用,它将从其工作表中获取缓存的结果行(“rewind”).如果要求线轴提供之前未见过的外部参考值,它将使用当前外部参考值运行其子树并缓存结果(“重新绑定”).惰性索引假脱机上的搜索谓词表示其工作表的密钥.
当假脱机检查新外部引用是否与之前看到的相同时,此特定计划形状中会出现此问题.嵌套循环连接正确更新其外部引用,并通过其PrepRecompute接口方法通知操作符其内部输入.在此检查开始时,内部运算符读取CParamBounds:FNeedToReload属性以查看外部引用是否从上次更改.示例堆栈跟踪如下所示:
当存在上面显示的子树时,特别是在使用连接的情况下,带有绑定的东西出错(可能是ByVal / ByRef / Copy问题),使得CParamBounds:FNeedToReload总是返回false,无论外部引用是否实际发生了变化.
当存在相同的子树但使用了Merge Union或Hash Union时,在每次迭代时都会正确设置此基本属性,并且每次都会根据需要对Lazy Index Spool进行倒带或重新绑定.顺便说一下,Distinct Sort和Stream Aggregate是无可指责的.我怀疑是Merge和Hash Union复制了以前的值,而Concatenation使用了引用.遗憾的是,在不访问sql Server源代码的情况下验证这一点几乎是不可能的.
最终的结果是,有问题的计划形状中的Lazy Index Spool总是认为它已经看到了当前的外部引用,通过搜索到其工作表来回复,通常什么都没找到,因此没有返回该外部引用的行.在调试器中逐步执行,假脱机只执行其RewindHelper方法,而不执行其ReloadHelper方法(在此上下文中重新加载=重新绑定).这在执行计划中很明显,因为假脱机下的运算符都具有’执行次数= 1′.
当然,例外是第一个外部引用,即Lazy Index Spool.这总是执行子树并在工作表中缓存结果行.所有后续迭代都会导致倒带,当当前迭代与外部引用具有与第一次相同的值时,将仅生成一行(单个缓存行).
因此,对于嵌套循环连接外侧的任何给定输入集,查询将返回与处理的第一行的重复项一样多的行(当然,第一行本身加一个).
演示
表格和样本数据:
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),(6);
以下(普通)查询使用合并联盟为每行(总共18个)生成正确的两个计数:
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;
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);
执行计划的形状有问题:
结果现在不正确,只有三行:
虽然不能保证这种行为,但是聚集索引扫描的第一行的c1值为1.还有另外两行具有此值,因此总共生成三行.
现在截断数据表并加载更多重复的’first’行:
TRUNCATE TABLE #T1; INSERT #T1 (c1) VALUES (1),(1);
现在连接计划是:
并且,如图所示,产生了8行,当然c1 = 1:
我注意到你已经开了一个Connect item for this bug,但实际上并不是报告产生影响的问题的地方.如果是这种情况,您真的应该联系Microsoft支持.