FAST_FORWARD 游标何时会有工作表(这是要避免的)?

Posted

技术标签:

【中文标题】FAST_FORWARD 游标何时会有工作表(这是要避免的)?【英文标题】:When will a FAST_FORWARD cursor have a work table (and is this something to avoid)? 【发布时间】:2011-12-13 17:26:25 【问题描述】:

背景

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

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

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

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

问题

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

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

附加信息

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

我也发现如果我尝试

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

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

但我也读到动态游标往往比静态游标(source 1、source 2),这让我感到惊讶,因为静态类型必须阅读源代码数据,复制它,然后读取副本,而不仅仅是读取源数据。 The article I referenced earlier 提到动态游标使用markers。谁能解释这些是什么?它只是一个 RID 或 CI 密钥,还是不同的东西?

脚本

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 

【问题讨论】:

一种解释可能是工作表提供了一些一致性。 top 5 在一个事务中检索,就像一个快照。如果没有工作表,您可能会得到一个 top 5,其中包含从未在表中一起出现的行。 @Andomar - 可能是这样的。在这种特定情况下,我使用的是本地 #temp 表,因此 SQL Server 可以(可能)识别出它无论如何都是一致的,因为其他事务无法修改它。我也刚刚尝试过SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED,但仍然看到相同的结果。 (并且SET ROWCOUNT 5 也保持两个计划不变) 【参考方案1】:

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

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

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

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

【讨论】:

对于TOP ... ORDER BY,可以通过通常遵循索引来满足,结果不会缓冲在假脱机或其他东西中。 TOP 迭代器只是添加到计划中,该计划计算行数并在达到目标时停止从子迭代器请求行。对于一般(非游标)查询,除非处于可序列化或快照隔离级别,否则不会尝试减轻在查询中插入或删除的行,并且如果移动中间扫描,确实可以读取同一行两次。不确定为什么带有 TOP 的游标需要更严格的语义? 游标和普通查询的区别在于普通查询在迭代时不能执行额外的DML语句。当您使用光标时,您可以。这些 DML 语句可能会影响 TOP...ORDER BY。 这是一个很好的观点 (+1)。这可能是一种万圣节保护问题。 另外,如果没有 ORDER BY,主要是更新(可以锁定,直到您通过物理行或页面)。这意味着只要您通过光标进行流式传输,表格就可以逐步解锁。使用 ORDER BY,情况并非如此,您可能需要一个宽表或索引锁,直到您关闭游标,否则您可能会得到不一致的结果。当您在 1000 行表上执行 TOP 5 时,这就是您想要的,因此查询优化器可能会决定最好提前制作快照,这样它就可以只锁定选定的行。 @Parmenion - 绝对没有你可以依赖的隐含的order by。如果您使用DISTINCT,您可能会看到您描述为排序操作的人工制品,但不能保证,因为它可能会使用哈希聚合。对于其他查询,由于所使用的访问方法,您可能会观察到该顺序是索引键顺序,但是您不能再次将其视为并行性,IAM 有序扫描或旋转木马扫描都可能改变这种观察到的行为。如果您需要ORDER BY,您应该明确指定它。【参考方案2】:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

【讨论】:

谢谢。游标内部的信息似乎很难获得!

以上是关于FAST_FORWARD 游标何时会有工作表(这是要避免的)?的主要内容,如果未能解决你的问题,请参考以下文章

sql 游标

SQL Server 临时表与游标

临时表不能在游标内工作以存储数据

如何知道何时使用事件将新超链接添加到工作表

如何从存储过程返回的游标将数据插入临时表

何时使用 MySQLdb 关闭游标