这是一个纯粹的学术问题,它不会引起问题,我只是想听听对这种行为的任何解释。
以标准问题 Itzik Ben-Gan 交叉连接 CTE 计数表为例:
USE [master]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[TallyTable]
(
@N INT
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
(
WITH
E1(N) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
) -- 1*10^1 or 10 rows
, E2(N) AS (SELECT 1 FROM E1 a, E1 b) -- 1*10^2 or 100 rows
, E4(N) AS (SELECT 1 FROM E2 a, E2 b) -- 1*10^4 or 10,000 rows
, E8(N) AS (SELECT 1 FROM E4 a, E4 b) -- 1*10^8 or 100,000,000 rows
SELECT TOP (@N) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS N FROM E8
)
GO
发出将创建 100 万行编号表的查询:
SELECT
COUNT(N)
FROM
dbo.TallyTable(1000000) tt
看看这个查询的并行执行计划:
请注意,在收集流运算符之前的“实际”行数是 1,004,588。在收集流运算符之后,行数是预期的 1,000,000。更奇怪的是,该值并不一致,并且会因运行而异。COUNT 的结果总是正确的。
再次发出查询,强制执行非并行计划:
SELECT
COUNT(N)
FROM
dbo.TallyTable(1000000) tt
OPTION (MAXDOP 1)
这一次,所有运算符都显示正确的“实际”行数。
到目前为止,我已经在 2005SP3 和 2008R2 上尝试过,两者的结果相同。关于可能导致这种情况的任何想法?
行在内部以数据包的形式从生产者线程传递到消费者线程(因此 CXPACKET - 类交换数据包),而不是一次一行。交易所内部有一定的缓冲。此外,从 Gather Streams 的消费者端关闭管道的调用必须在控制数据包中传递回生产者线程。调度和其他内部考虑意味着并行计划总是有一定的“停止距离”。
因此,您经常会看到这种行计数差异,其中实际需要的子树的潜在行集少于整个潜在行集。在这种情况下,TOP 使执行“提前结束”。
更多信息:
我想我可能对此有部分解释,但请随时将其击落或发布任何替代方案。@MartinSmith 通过在执行计划中强调 TOP 的影响,肯定会有所作为。
简单来说,“Actual Row Count”不是算子处理的行数,而是算子的 GetNext() 方法被调用的次数。
取自BOL:
为了完整起见,并行运算符的一些背景知识很有用。工作通过重新分区流或分发流操作员以并行计划分配到多个流。它们使用以下四种机制之一在线程之间分配行或页面:
第一个分发流操作符(计划中的最右侧)对源自恒定扫描的行使用需求分区。共有三个线程调用 GetNext() 6、4 和 0 次,总共 10 个“实际行”:
在下一个分发操作符处,我们再次拥有三个线程,这次分别调用了 50、50 和 0 次 GetNext(),总共 100:
原因和解释可能出现在下一个并行算子上。
所以我们现在有 11 次对 GetNext() 的调用,而我们预计会看到 10 次。
编辑:2011-11-13
卡在这一点上,我与聚集索引中的小伙子一起兜售答案,@MikeWalsh 亲切地在这里指导@SQLKiwi 。
1,004,588
在我的测试中也经常出现这个数字。对于下面更简单的计划,我也看到了这一点。
执行计划中其他有趣的数字是
我的猜测是,因为任务是并行处理的,所以当另一个任务将第 100 万行交付给收集流操作员时,一个任务处于飞行中处理行,因此正在处理额外的行。此外,从这篇文章中,行被缓冲并分批交付给这个迭代器,因此在任何情况下,被处理的行数似乎很可能会超过而不是完全达到
TOP
规范。编辑
只是更详细地看一下这个。我注意到我得到的变化不仅仅是
1,004,588
上面引用的行数,所以在循环中运行上面的查询进行了 1000 次迭代并捕获了实际的执行计划。丢弃并行度为零的81个结果,得到以下数字。可以看出,1,004,588 是迄今为止最常见的结果,但有 3 次出现了最坏的情况,并且处理了 100,000,000 行。观察到的最佳情况是 1,000,496 行计数,发生了 19 次。
重现的完整脚本位于此答案的修订版 2 的底部(如果在具有 2 个以上处理器的系统上运行,则需要对其进行调整)。
我认为问题出在这样一个事实,即多个流可以处理同一行,具体取决于行在流之间的划分方式。