为什么串联运算符估计的行数少于其输入的行数?


20

在下面的查询计划摘要中,很明显,该Concatenation运算符的行估计应为~4.3 billion rows,或其两个输入的行估计之和。

但是,~238 million rows会产生一个估计值,从而导致次优Sort/ Stream Aggregate策略,该策略会将数百GB的数据溢出到tempdb。在这种情况下,逻辑上一致的估计将产生Hash Aggregate,消除了溢出,并显着提高了查询性能。

这是SQL Server 2014中的错误吗?在任何合理的情况下,估算值低于输入值可能是合理的?可能有哪些解决方法?

在此处输入图片说明

这是完整的查询计划(匿名)。我没有对该服务器的sysadmin访问权限,无法提供来自QUERYTRACEON 2363或类似跟踪标记的输出,但是如果有帮助的话,也许可以从管理员那里获取这些输出。

该数据库的兼容性级别为120,因此正在使用新的SQL Server 2014基数估计器。

每次加载数据时都会手动更新统计信息。给定数据量,我们当前正在使用默认采样率。较高的采样率(或FULLSCAN)可能会产生影响。

Answers:


21

此Connect项目上引用Campbell Fraser :

这些“基数不一致”可能发生在许多情况下,包括使用concat时。之所以会出现这种情况,是因为最终计划中特定子树的估计可能已经在结构不同但逻辑上等效的子树上进行了。由于基数估计的统计性质,不能保证在不同但逻辑上等效的树上进行估计得到相同的估计。因此,总体上不能保证预期的一致性。

稍微扩展一下:我喜欢解释的方式是说,初始基数估计(在基于成本的优化开始之前执行)会产生更多的“一致”基数估计,因为整个初始树都经过处理,并且每个后续树估计直接取决于前一个。

在基于成本的优化过程中,可能会探索计划树的一部分(一个或多个运算符),并用其他替代方案替代,每种替代方案都可能需要新的基数估计。没有一般的方法可以说哪个估计值通常会好于另一个估计值,因此很有可能最终得出看起来“前后不一致”的最终计划。这仅仅是将“一些计划”缝合在一起以形成最终安排的结果。

综上所述,SQL Server 2014中引入的新基数估计器(CE)进行了一些详细的更改,这使其比原始CE 有所减少。

除了升级到最新的累积更新并检查是否已启用4199的优化程序修复之外,您的主要选择是尝试统计信息/索引更改(注意缺少索引的警告)和更新,或者以不同的方式表示查询。目的是获得显示您所需行为的计划。例如,然后可以使用计划指南将其冻结。

匿名计划使评估细节变得困难,但是我还将仔细查看位图,以查看它们是否属于“优化”(Opt_Bitmap)或后优化(Bitmap)类型。我也对过滤器感到怀疑。

但是,如果行计数准确无误,这似乎可以从列存储中受益。除了通常的好处外,您还可以利用批处理模式运算符的动态内存授予(可能需要跟踪标志9389)。


7

在SQL Server 2012(11.0.6020)上构建一个公认非常简单的测试平台,使我可以重新创建一个计划,其中两个哈希匹配查询通过串联UNION ALL。我的测试台没有显示您看到的错误估计。也许这 SQL Server 2014 CE问题。

对于一个实际上返回280行的查询,我得到了133.785行的估计,但是这是可以预期的,因为我们将在下面进一步介绍:

IF OBJECT_ID('dbo.Union1') IS NOT NULL
DROP TABLE dbo.Union1;
CREATE TABLE dbo.Union1
(
    Union1_ID INT NOT NULL
        CONSTRAINT PK_Union1
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , Union1_Text VARCHAR(255) NOT NULL
    , Union1_ObjectID INT NOT NULL
);

IF OBJECT_ID('dbo.Union2') IS NOT NULL
DROP TABLE dbo.Union2;
CREATE TABLE dbo.Union2
(
    Union2_ID INT NOT NULL
        CONSTRAINT PK_Union2
        PRIMARY KEY CLUSTERED
        IDENTITY(2,2)
    , Union2_Text VARCHAR(255) NOT NULL
    , Union2_ObjectID INT NOT NULL
);

INSERT INTO dbo.Union1 (Union1_Text, Union1_ObjectID)
SELECT o.name, o.object_id
FROM sys.objects o;

INSERT INTO dbo.Union2 (Union2_Text, Union2_ObjectID)
SELECT o.name, o.object_id
FROM sys.objects o;
GO

SELECT *
FROM dbo.Union1 u1
    INNER HASH JOIN sys.objects o ON u1.Union1_ObjectID = o.object_id
UNION ALL
SELECT *
FROM dbo.Union2 u2
    INNER HASH JOIN sys.objects o ON u2.Union2_ObjectID = o.object_id;

认为原因是缺少两个统一的结果联接的统计信息。在缺乏统计信息的情况下,大多数情况下,SQL Server需要围绕列的选择性进行有根据的猜测。

乔·萨克(Joe Sack)在这里对此感兴趣。

对于a UNION ALL,可以肯定地说,我们将确切地看到联合的每个组件返回的总行数,但是,由于SQL Server 对的两个组件都使用行估计,因此UNION ALL我们看到它将两个行的总估计行相加查询以得出串联运算符的估计值。

在上面的示例中,每个部分的估计行数UNION ALL为66.8927,总和等于133.785,对于串联运算符,我们看到了估计行数。

上面的联合查询的实际执行计划如下:

在此处输入图片说明

您可以看到“估计”与“实际”的行数。就我而言,将两个哈希匹配运算符返回的“估计”行数相加,就等于串联运算符显示的数量。

我会尝试从问题中显示的Paul White的帖子中建议从跟踪2363等获得输出。或者,您可以尝试OPTION (QUERYTRACEON 9481)在查询中使用还原到70 CE版本,以查看是否可以“解决”该问题。


1
谢谢。我肯定已经看到“原因在于缺少两个统一的结果连接的统计信息”对随后的连接或聚合(在UNION之后发生)有很大的影响。根据我的经验,SQL 2014实际上比SQL 2012更好地处理了此问题。例如,这是我过去使用的简单测试脚本:gist.github.com/anonymous/1497112d8b25ab8fb782a04569959c68 但是,我不认为连接运算符需要与联接有关的值分布信息。可能需要。
Geoff Patterson

我同意你的看法串联不应该需要统计数据来准确地执行。它应该简单地能够可靠地将传入的行估计值相加,以很好地了解将要输出的行数。正如@PaulWhite在他的回答中所示,并非总是如此。对我来说,这里的好处是看似简单,但实际上却并非如此。我真的很高兴您以这种方式问了这个问题,我只希望您不必匿名该计划-看到实际的查询会很有趣。
Max Vernon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.