AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 7233
Accepted
Mark Storey-Smith
Mark Storey-Smith
Asked: 2011-10-26 15:22:42 +0800 CST2011-10-26 15:22:42 +0800 CST 2011-10-26 15:22:42 +0800 CST

并行计划中的“实际”行数不准确

  • 772

这是一个纯粹的学术问题,它不会引起问题,我只是想听听对这种行为的任何解释。

以标准问题 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 上尝试过,两者的结果相同。关于可能导致这种情况的任何想法?

sql-server
  • 4 4 个回答
  • 1533 Views

4 个回答

  • Voted
  1. Best Answer
    Paul White
    2011-11-14T13:58:26+08:002011-11-14T13:58:26+08:00

    行在内部以数据包的形式从生产者线程传递到消费者线程(因此 CXPACKET - 类交换数据包),而不是一次一行。交易所内部有一定的缓冲。此外,从 Gather Streams 的消费者端关闭管道的调用必须在控制数据包中传递回生产者线程。调度和其他内部考虑意味着并行计划总是有一定的“停止距离”。

    因此,您经常会看到这种行计数差异,其中实际需要的子树的潜在行集少于整个潜在行集。在这种情况下,TOP 使执行“提前结束”。

    更多信息:

    • 并行执行计划很糟糕
    • 了解和使用 SQL Server 中的并行性。
    • 12
  2. Mark Storey-Smith
    2011-10-27T07:42:58+08:002011-10-27T07:42:58+08:00

    我想我可能对此有部分解释,但请随时将其击落或发布任何替代方案。@MartinSmith 通过在执行计划中强调 TOP 的影响,肯定会有所作为。

    简单来说,“Actual Row Count”不是算子处理的行数,而是算子的 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>
    

    在下一个分发操作符处,我们再次拥有三个线程,这次分别调用了 50、50 和 0 次 GetNext(),总共 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>
    

    原因和解释可能出现在下一个并行算子上。

    <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>
    

    所以我们现在有 11 次对 GetNext() 的调用,而我们预计会看到 10 次。

    编辑:2011-11-13

    卡在这一点上,我与聚集索引中的小伙子一起兜售答案,@MikeWalsh 亲切地在这里指导@SQLKiwi 。

    • 10
  3. Martin Smith
    2011-10-27T06:39:51+08:002011-10-27T06:39:51+08:00

    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
    

    计划

    执行计划中其他有趣的数字是

    +----------------------------------+--------------+--------------+-----------------+
    |                                  | 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 |
    +----------------------------------+--------------+--------------+-----------------+
    

    我的猜测是,因为任务是并行处理的,所以当另一个任务将第 100 万行交付给收集流操作员时,一个任务处于飞行中处理行,因此正在处理额外的行。此外,从这篇文章中,行被缓冲并分批交付给这个迭代器,因此在任何情况下,被处理的行数似乎很可能会超过而不是完全达到TOP规范。

    编辑

    只是更详细地看一下这个。我注意到我得到的变化不仅仅是1,004,588上面引用的行数,所以在循环中运行上面的查询进行了 1000 次迭代并捕获了实际的执行计划。丢弃并行度为零的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
    

    可以看出,1,004,588 是迄今为止最常见的结果,但有 3 次出现了最坏的情况,并且处理了 100,000,000 行。观察到的最佳情况是 1,000,496 行计数,发生了 19 次。

    重现的完整脚本位于此答案的修订版 2 的底部(如果在具有 2 个以上处理器的系统上运行,则需要对其进行调整)。

    • 7
  4. mrdenny
    2011-10-26T18:20:56+08:002011-10-26T18:20:56+08:00

    我认为问题出在这样一个事实,即多个流可以处理同一行,具体取决于行在流之间的划分方式。

    • 1

相关问题

  • SQL Server - 使用聚集索引时如何存储数据页

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    您如何显示在 Oracle 数据库上执行的 SQL?

    • 2 个回答
  • Marko Smith

    如何选择每组的第一行?

    • 6 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    我可以查看在 SQL Server 数据库上运行的历史查询吗?

    • 6 个回答
  • Marko Smith

    如何在 PostgreSQL 中使用 currval() 来获取最后插入的 id?

    • 10 个回答
  • Marko Smith

    如何在 Mac OS X 上运行 psql?

    • 11 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Marko Smith

    将数组参数传递给存储过程

    • 12 个回答
  • Martin Hope
    Manuel Leduc PostgreSQL 多列唯一约束和 NULL 值 2011-12-28 01:10:21 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Stuart Blackler 什么时候应该将主键声明为非聚集的? 2011-11-11 13:31:59 +0800 CST
  • Martin Hope
    pedrosanta 使用 psql 列出数据库权限 2011-08-04 11:01:21 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST
  • Martin Hope
    BrunoLM Guid vs INT - 哪个更好作为主键? 2011-01-05 23:46:34 +0800 CST
  • Martin Hope
    bernd_k 什么时候应该使用唯一约束而不是唯一索引? 2011-01-05 02:32:27 +0800 CST
  • Martin Hope
    Patrick 如何优化大型数据库的 mysqldump? 2011-01-04 13:13:48 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve