这是一个纯粹的学术问题,以至于它不会引起问题,我只是想听听对这种行为的任何解释。
以标准问题 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
Run Code Online (Sandbox Code Playgroud)
发出将创建 100 万行号表的查询:
SELECT
COUNT(N)
FROM
dbo.TallyTable(1000000) tt
Run Code Online (Sandbox Code Playgroud)
看看这个查询的并行执行计划:
请注意,收集流运算符之前的“实际”行数为 1,004,588。在收集流操作符之后,行数是预期的 1,000,000。更奇怪的是,该值并不一致,并且会因运行而异。COUNT 的结果总是正确的。
再次发出查询,强制非并行计划:
SELECT
COUNT(N)
FROM
dbo.TallyTable(1000000) tt
OPTION (MAXDOP 1)
Run Code Online (Sandbox Code Playgroud)
这次所有运算符都显示正确的“实际”行数。
到目前为止,我已经在 2005SP3 和 2008R2 上尝试过这个,两者的结果相同。关于可能导致这种情况的任何想法?
Pau*_*ite 12
行在内部从生产者线程以数据包的形式传递到消费者线程(因此是 CXPACKET - 类交换数据包),而不是一次一行。交易所内部有一定的缓冲。此外,从 Gather Streams 的消费者端关闭管道的调用必须在控制数据包中传递回生产者线程。调度和其他内部考虑意味着并行计划总是有一定的“停止距离”。
因此,您经常会看到这种行数差异,其中实际需要的行数少于子树的整个潜在行集。在这种情况下,TOP 使执行“提前结束”。
更多信息:
Mar*_*ith 10
我想我可能对此有部分解释,但请随时将其驳回或发布任何替代方案。@MartinSmith 通过在执行计划中突出显示 TOP 的效果,肯定会有所作为。
简而言之,“实际行数”不是运算符处理的行数,而是运算符的 GetNext() 方法被调用的次数。
取自BOL:
物理操作符初始化、收集数据和关闭。具体来说,物理操作员可以回答以下三个方法调用:
- Init():Init() 方法使物理运算符初始化自身并设置任何所需的数据结构。物理操作员可能会收到许多 Init() 调用,但通常物理操作员只会收到一个。
- GetNext():GetNext() 方法使物理运算符获取第一行或后续数据行。物理操作员可能会收到零个或多个 GetNext() 调用。
- Close():Close() 方法使物理操作符执行一些清理操作并自行关闭。物理操作员只收到一个 Close() 调用。
GetNext() 方法返回一行数据,它被调用的次数在使用 SET STATISTICS PROFILE ON 或 SET STATISTICS XML ON 生成的 Showplan 输出中显示为 ActualRows。
为了完整起见,了解一些并行运算符的背景知识很有用。工作通过重新分区流或分发流操作符以并行计划分发到多个流。它们使用以下四种机制之一在线程之间分配行或页面:
第一个分布式流操作符(在计划中的最右边)对源自恒定扫描的行使用需求分区。有三个线程调用 GetNext() 6、4 和 0 次,总共 10 个“实际行”:
<RunTimeInformation>
<RunTimeCountersPerThread Thread="2" ActualRows="6" ActualEndOfScans="1" ActualExecutions="1" />
<RunTimeCountersPerThread Thread="1" ActualRows="4" ActualEndOfScans="1" ActualExecutions="1" />
<RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
</RunTimeInformation>
Run Code Online (Sandbox Code Playgroud)
在下一个分布操作符中,我们再次拥有三个线程,这次分别对 GetNext() 进行了 50、50 和 0 次调用,总共 100 次:
<RunTimeInformation>
<RunTimeCountersPerThread Thread="2" ActualRows="50" ActualEndOfScans="1" ActualExecutions="1" />
<RunTimeCountersPerThread Thread="1" ActualRows="50" ActualEndOfScans="1" ActualExecutions="1" />
<RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
</RunTimeInformation>
Run Code Online (Sandbox Code Playgroud)
原因和解释可能出现在下一个并行运算符处。
<RunTimeInformation>
<RunTimeCountersPerThread Thread="2" ActualRows="1" ActualEndOfScans="0" ActualExecutions="1" />
<RunTimeCountersPerThread Thread="1" ActualRows="10" ActualEndOfScans="0" ActualExecutions="1" />
<RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
</RunTimeInformation>
Run Code Online (Sandbox Code Playgroud)
所以我们现在有 11 次对 GetNext() 的调用,我们期望看到 10 次。
编辑:2011-11-13
在这一点上卡住了,我和聚集索引中的小伙子一起兜售答案,@MikeWalsh在这里指导@SQLKiwi 。
1,004,588
这个数字在我的测试中也经常出现。
我也看到了下面稍微简单的计划。
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
SELECT * INTO #E4 FROM E4;
WITH E8(N) AS (SELECT 1 FROM #E4 a, #E4 b),
Nums(N) AS (SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) FROM E8 )
SELECT COUNT(N) FROM Nums
DROP TABLE #E4
Run Code Online (Sandbox Code Playgroud)
执行计划中其他感兴趣的数字是
+----------------------------------+--------------+--------------+-----------------+
| | Table Scan A | Table Scan B | Row Count Spool |
+----------------------------------+--------------+--------------+-----------------+
| Number Of Executions | 2 | 2 | 101 |
| Actual Number Of Rows - Total | 101 | 20000 | 1004588 |
| Actual Number Of Rows - Thread 0 | - | | |
| Actual Number Of Rows - Thread 1 | 95 | 10000 | 945253 |
| Actual Number Of Rows - Thread 2 | 6 | 10000 | 59335 |
| Actual Rebinds | 0 | 0 | 2 |
| Actual Rewinds | 0 | 0 | 99 |
+----------------------------------+--------------+--------------+-----------------+
Run Code Online (Sandbox Code Playgroud)
我的猜测只是因为任务是并行处理的,一个任务在飞行中处理行,而另一个任务将第百万行传递给收集流操作符,因此正在处理额外的行。此外,从本文中,行被缓冲并分批传送到此迭代器,因此TOP
在任何情况下,正在处理的行数似乎很可能会超过而不是完全达到规范。
编辑
只是更详细地看一下这个。我注意到我得到的变化不仅仅是1,004,588
上面引用的行数,所以在循环中运行上面的查询 1,000 次迭代并捕获实际的执行计划。丢弃并行度为零的 81 个结果,得出以下数字。
count Table Scan A: Total Actual Row Spool - Total Actual Rows
----------- ------------------------------ ------------------------------
352 101 1004588
323 102 1004588
72 101 1003565
37 101 1002542
35 102 1003565
29 101 1001519
18 101 1000496
13 102 1002542
5 9964 99634323
5 102 1001519
4 9963 99628185
3 10000 100000000
3 9965 99642507
2 9964 99633300
2 9966 99658875
2 9965 99641484
1 9984 99837989
1 102 1000496
1 9964 99637392
1 9968 99671151
1 9966 99656829
1 9972 99714117
1 9963 99629208
1 9985 99847196
1 9967 99665013
1 9965 99644553
1 9963 99623626
1 9965 99647622
1 9966 99654783
1 9963 99625116
Run Code Online (Sandbox Code Playgroud)
可以看出,1,004,588 是迄今为止最常见的结果,但有 3 次发生了最坏的情况,处理了 100,000,000 行。观察到的最佳情况是 1,000,496 行计数,发生了 19 次。
要重现的完整脚本位于此答案修订版 2 的底部(如果在具有 2 个以上处理器的系统上运行,则需要进行调整)。
归档时间: |
|
查看次数: |
1465 次 |
最近记录: |