下面是一个简单的示例,它返回的结果很奇怪,这些结果是无法预测的,我们无法在团队中进行解释。我们是在做错什么还是SQL Server错误?

经过一番调查,我们在子查询中将搜索区域缩小为union子句,该子句从“ men”表中选择一条记录

它在SQL Server 2000中正常工作(返回12行),但是在2008年和2012年,它仅返回一行。

1 2

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)


取消注释最后一行,它给出:2 2 2

有很多奇怪的行为:


经过一系列放置,创建,截断并插入“ men”表后,它有时可以工作(返回12行)
当您将“ union select men.wife_id”更改为“ union all select men”时.wife_id”或“ union select isull(men.wife_id,null)”(!!!),它将返回12行(按预期)。
奇怪的行为似乎与“ wife_id”列的数据类型无关。我们在具有更大数据集的开发系统上观察到了它。
“ whereife_id> 0”将返回6行。
我们还观察到了这种语句对视图的奇怪行为。 SELECT *返回行的子集,SELECT TOP 1000返回全部


#1 楼


我们在做错什么还是SQL Server错误?

这是一个错误结果错误,您应该通过通常的支持渠道进行报告。如果您没有支持协议,则如果Microsoft将行为确认为错误,则可以知道通常可以退还已支付的事件。
该错误需要三个要素:

嵌套循环外部引用(适用)
在外部引用上寻找的内部懒惰索引后台处理
内部串联运算符

例如,问题中的查询产生如下计划:

有很多方法可以删除这些元素之一,因此该错误不再重现。
例如,可以创建索引或统计信息表示优化器选择不使用惰性索引假脱机。或者,可以使用提示来强制执行哈希或合并并集,而不是使用串联。人们还可以重写查询以表达相同的语义,但是这导致缺少一个或多个所需元素的不同计划形状。
更多详细信息
惰性索引假脱机惰性地缓存内部结果工作表中的行(由外部引用(相关参数)值索引)。如果向惰性索引假脱机请求了以前已经看到的外部引用,它将从其工作表中获取缓存的结果行(“倒带”)。如果要求线轴提供以前从未见过的外部参考值,它将使用当前外部参考值运行其子树并缓存结果(“重新绑定”)。惰性索引假脱机上的查找谓词指示其工作表的键。
当线轴检查以查看新的外部参照是否与之前看到的相同时,就会在该特定的平面形状中发生问题。嵌套循环连接正确更新其外部引用,并通过其PrepRecompute接口方法在其内部输入上通知运算符。在此检查开始时,内部运算符会读取CParamBounds:FNeedToReload属性,以查看外部引用是否已与上次更改。下面是一个示例堆栈跟踪:

当存在上面显示的子树时,特别是在使用Concatenation的情况下,绑定出现问题(例如qq1202079q)返回false,无论外部引用是否实际更改。
当存在相同的子树但使用合并联合或哈希联合时,此必要属性在每次迭代中均正确设置,并且懒惰索引假脱机回绕或视情况重新绑定。顺便说一句,非重复排序和流聚合是无可指摘的。我的怀疑是Merge和Hash Union复制了先前的值,而Concatenation使用了引用。不幸的是,如果没有访问SQL Server源代码,几乎不可能验证这一点。
最终结果是,有问题的计划形状中的惰性索引假脱机总是认为它已经看到了当前的外部引用,倒退寻找到它的工作表中,通常什么也找不到,因此没有返回该外部引用的行。在调试器中逐步执行该过程,假脱机程序只会执行其CParamBounds:FNeedToReload方法,而不会执行其RewindHelper方法(在这种情况下,reload = rebind)。这在执行计划中很明显,因为线轴下的操作员都具有“执行次数= 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);

以下(简单)查询使用合并联合为每行(每个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;

执行计划的形状有问题:

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

虽然不能保证此行为,但是聚簇索引扫描的第一行的ReloadHelper值为1。另外两行具有此值,因此三行
现在截断数据表并为它添加“第一”行的更多副本:
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);

现在的连接计划是: >然后,如图所示,产生了8行,当然都带有c1:对生产的影响。如果真是这样,您真的应该联系Microsoft支持。

此错误结果错误已在某个阶段得到修复。从2012年起,它不再为我在任何版本的SQL Server上复制。它确实在SQL Server 2008 R2 SP3-GDR内部版本10.50.6560.0(X64)上进行复制。

#2 楼

为什么使用不带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


评论


是的,这可行,但是我的版本也应该可行。上面的抽象示例是生产查询的简化版本,这更加有意义。

– Ryszard Bocian
2015年4月20日14:20在