FAST_FORWARD游标何时会有一个工作表(这是否应该避免)?

Mar*_*ith 31 sql sql-server cursor sql-server-2008 database-performance

背景

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

取

并且实际计划显示了聚集索引扫描的重复提取

获取扫描

在其他场合(例如,当向TOP查询添加a时),估计的计划显示填充工作表的"人口查询"阶段

获取并填充

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

寻求

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

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

附加信息

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

我也发现,如果我尝试

SET @C2 = CURSOR DYNAMIC TYPE_WARNING FOR SELECT TOP ...
Run Code Online (Sandbox Code Playgroud)

光标被隐式转换为keyset游标,因此很明显TOP动态游标不支持该构造,也许是出于Ruben的答案的原因 - 仍在寻找对此的明确解释.

但是我还读到,动态游标往往比它们的静态对应物(源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 
Run Code Online (Sandbox Code Playgroud)

Rub*_*ben 9

只是预感,但通常TOP-ORDER BY要求SQL Server以某种方式缓冲结果(索引扫描的结果或整个结果在临时结构中,或两者之间的任何结果).

有人可能会争辩说,对于游标,即使按主键排序(如在您的示例中)也是如此,因为当相应的SELECT确实返回5行时,您不能允许TOP 5游标意外返回少于5行(或者更糟糕的是:游标返回超过5行).

当有在桌子上删除或插入这种奇怪的情况在理论上发生索引扫描的范围已经被确定为光标,在插入/删除秋天索引扫描的范围之内,但你还没有完成提取.为了防止这种情况发生,他们可能会在这里安全地犯错.(他们只是没有针对#temp表进行优化.)

但问题是:SQL Server是否允许FETCH FROM SELECT TOP n没有ORDER BY子句?(没有在这里运行的SQL Server实例.)可能有趣的是知道导致什么计划.

  • @Parmenion - 肯定没有你可以依赖的隐含的`order by`.如果使用`DISTINCT`,您可能会将您描述的行为视为排序操作的假象,但不能保证,因为它可能会使用散列聚合.对于其他查询,您可能会发现由于使用了访问方法,订单处于索引键顺序中,但您不能再依赖于此作为并行,IAM命令扫描或旋转木马扫描都可以改变这种观察到的行为.如果你需要`ORDER BY`,你应该明确指定它. (2认同)

小智 5

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

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

我是否正确地认为第一种方法(没有额外的工作表填充步骤)更有效率?

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

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

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

我还读过,动态游标往往比静态游标慢.

对于Transact-SQL游标,通常会这样,其中触摸了大部分或全部游标集.保存和恢复动态查询计划的状态会产生相关成本.如果在每次调用时处理单行,并且最终触及所有行,则会最大化此保存/恢复开销.

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

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

如果有人能解释为什么第一个查询中的每个扫描计为2个逻辑读取,也可能是非常有启发性的

这取决于为扫描保存状态的方式,以及计算读取的方式.

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

动态游标计划中的每个迭代器都存在标记,而不仅仅是访问方法.'标记'是在它停止时重新启动计划迭代器所需的所有状态信息.对于访问方法,RID或索引键(如有必要,带有uniquifier)是其中很重要的一部分,但无论如何都不是整个故事.