33

背景

我注意到在尝试运行总查询时,有时估计的计划只显示“获取查询”

拿来

并且实际计划显示从聚集索引扫描中重复获取

获取扫描

在其他情况下(例如TOP,在查询中添加 a 时),估计的计划显示填充工作表的“Population Query”阶段

获取和填充

实际计划显示聚集索引扫描以填充工作表,然后重复查找该工作表。

寻求

问题

  1. SQL Server 在选择一种方法而不是另一种方法时使用什么标准?
  2. 我认为第一种方法(没有额外的工作表填充步骤)更有效吗?

(额外的问题:如果有人能解释为什么第一个查询中的每次扫描都算作 2 次逻辑读取,这可能也很有启发性)

附加信息

我在这里找到了这篇文章,它解释了FAST_FORWARD游标可以使用动态计划或静态计划。在这种情况下,第一个查询似乎使用动态计划,第二个查询似乎使用静态计划。

我还发现,如果我尝试

SET @C2 = CURSOR DYNAMIC TYPE_WARNING FOR SELECT TOP ...

游标被隐式转换为keyset游标,因此很明显TOP动态游标不支持该构造,这可能是鲁本回答中的原因 - 仍在寻找对此的明确解释。

然而,我也读到动态游标往往比它们的静态游标(源 1源 2 ),这让我感到惊讶,因为静态类型必须读取源数据,复制它,然后读取副本而不仅仅是读取源数据。我之前引用的文章提到动态游标使用. 谁能解释这些是什么?它只是一个 RID 或 CI 密钥,还是不同的东西?markers

脚本

SET STATISTICS IO OFF

CREATE TABLE #T ( ord INT IDENTITY PRIMARY KEY, total INT, Filler char(8000))

INSERT INTO #T (total) VALUES (37),(80),(55),(31),(53)

DECLARE @running_total INT, 
    @ord INT, 
    @total INT
    
SET @running_total = 0
SET STATISTICS IO ON
DECLARE @C1 AS CURSOR;
SET @C1 = CURSOR FAST_FORWARD FOR SELECT ord, total FROM #T ORDER BY ord;
OPEN @C1;
PRINT 'Initial FETCH C1'
FETCH NEXT FROM @C1 INTO @ord, @total ;
WHILE @@FETCH_STATUS = 0
BEGIN
  SET @running_total = @running_total + @total
  PRINT 'FETCH C1'
  FETCH NEXT FROM @C1 INTO @ord, @total ;
END

SET @running_total = 0
SET STATISTICS IO ON
DECLARE @C2 AS CURSOR;
SET @C2 = CURSOR FAST_FORWARD FOR SELECT TOP 5 ord, total FROM #T ORDER BY ord;
OPEN @C2;
PRINT 'Initial FETCH C2'
FETCH NEXT FROM @C2 INTO @ord, @total ;
WHILE @@FETCH_STATUS = 0
BEGIN
  SET @running_total = @running_total + @total
  PRINT 'FETCH C2'
  FETCH NEXT FROM @C2 INTO @ord, @total ;
END

PRINT 'End C2'
DROP TABLE #T 
4

2 回答 2

9

只是预感,但通常 TOP-ORDER BY 需要 SQL Server 以某种方式缓冲结果(索引扫描的结果或实际上是临时结构中的整个结果,或介于两者之间的任何内容)。

有人可能会争辩说,对于游标,即使按主键排序(如您的示例),这也是必要的,因为当相应的 SELECT 确实返回 5 行(或更糟糕的是:游标返回超过 5 行)。

这种奇怪的情况理论上可能发生在已经为游标确定索引扫描范围之后表上有删除或插入,并且插入/删除在索引扫描范围内,但您还没有完成获取。为了防止这种情况发生,他们可能会在安全方面犯错。(而且他们只是没有针对#temp 表进行优化。)

但是有一个问题:SQL Server 是否允许FETCH FROM SELECT TOP n没有ORDER BY子句?(这里还没有运行 SQL Server 实例。)了解导致的计划可能会很有趣。

于 2011-10-26T00:52:14.990 回答
5

SQL Server 在选择一种方法而不是另一种方法时使用什么标准?

这主要是基于成本的决策。引用您链接到的文章,“在动态计划看起来很有希望的情况下,可能会试探性地跳过成本比较。这主要发生在非常便宜的查询中,尽管细节很深奥。”

我认为第一种方法(没有额外的工作表填充步骤)更有效吗?

这取决于。动态和静态游标计划有不同的优点和缺点。如果最终将触及所有行,则静态计划可能会执行得更好。稍后再详细介绍。

很明显,TOP动态游标不支持该构造

这是真的。动态游标计划中的所有迭代器必须能够保存和恢复状态,向前和向后扫描,为每个输出行处理一个输入行,并且是非阻塞的。一般来说,Top 不能满足所有这些要求;该类CQScanTopNew没有实现必要的Set/Get/Goto/Marker(), 和ReverseDirection()方法(等等)。

我还读到动态游标往往比静态游标慢。

对于 Transact-SQL 游标,这通常是正确的,其中大部分或全部游标集都被触及。保存和恢复动态查询计划的状态是有成本的。在每次调用处理单行并且最终触及所有行的情况下,这种保存/恢复开销最大化。

静态游标具有制作集合副本的开销(这可能是大型集合的主要因素),但检索的每行成本非常小。键集具有比静态更高的每行检索开销,因为它们必须外部连接回源表才能检索非键列。

当访问集的相对较小部分时,动态游标是最佳的,和/或检索不是一次一行。这是许多常见游标场景中的典型访问模式,但不是博客文章倾向于测试的:)

如果有人能解释为什么第一个查询中的每次扫描都算作 2 次逻辑读取,那可能也很有启发性

这取决于为扫描保存状态的方式,以及读取次数的计数方式。

我之前引用的文章提到动态游标使用标记。谁能解释这些是什么?它只是一个 RID 或 CI 密钥,还是不同的东西?

动态游标计划中的每个迭代器都存在标记,而不仅仅是访问方法。“标记”是在停止点重新启动计划迭代器所需的所有状态信息。对于访问方法,RID 或索引键(必要时带有唯一性)是其中的重要部分,但无论如何都不是全部。

于 2014-11-21T13:06:57.540 回答