为什么人们如此讨厌SQL游标?

时间:2008-11-13 16:39:15

标签: sql cursor

我可以理解为了避免由于开销和不便而不得不使用光标,但看起来有一些严重的光标 - 恐惧症 - 狂热正在进行,人们将不遗余力地避免使用光标。 / p>

例如,有一个问题询问如何使用游标执行明显无关紧要的操作,并使用带有递归自定义函数的公用表表达式(CTE)递归查询提出接受的答案,即使这限制了可能的行数处理到32(由于sql server中的递归函数调用限制)。这让我觉得它是系统寿命的一个糟糕的解决方案,更不用说为了避免使用简单的游标而付出的巨大努力。

这种疯狂仇恨的原因是什么?有一些“着名的权威”发布了针对游标的法特瓦吗?一些无法形容的邪恶是否潜伏在游戏的核心,腐蚀了儿童的道德或其他东西?

维基问题,答案比代表更感兴趣。

相关信息:

SQL Server Fast Forward Cursors

编辑:让我更准确一点:我知道不应该使用游标而不是正常的关系操作;这是一个明智的选择。我不明白的是,即使游标是一种更简单和/或更有效的解决方案,人们也会不顾一切地避开光标,就像他们有傻瓜一样。这种不合理的仇恨让我感到困惑,而不是明显的技术效率。

14 个答案:

答案 0 :(得分:73)

游标的“开销”仅仅是API的一部分。游标是RDBMS的部分工作方式。通常CREATE TABLEINSERT都有SELECT语句,实现是明显的内部游标实现。

使用更高级别的“基于集合的运算符”将光标结果捆绑到一个结果集中,这意味着API来回减少。

游标早于提供一流集合的现代语言。旧C,COBOL,Fortran等不得不一次处理一行,因为没有可以广泛使用的“集合”概念。 Java,C#,Python等具有包含结果集的一流列表结构。

缓慢的问题

在某些圈子中,关系连接是个谜,人们会编写嵌套游标而不是简单连接。我已经看到了真正的史诗嵌套循环操作,它被写成了很多很多游标。击败RDBMS优化。而且跑得很慢。

简单SQL重写以使用连接替换嵌套的游标循环,并且单个平面游标循环可以使程序在第100次运行。 [他们以为我是优化之神。我所做的只是用连接替换嵌套循环。仍然使用游标。]

这种混乱常常导致对游标的起诉。但是,它不是光标,而是光标的误用就是问题。

尺寸问题

对于真正的史诗结果集(即将表转储到文件中),游标是必不可少的。基于集合的操作无法将非常大的结果集实现为内存中的单个集合。

<强>替代

我尝试尽可能多地使用ORM层。但这有两个目的。首先,游标由ORM组件管理。其次,SQL与应用程序分离为配置文件。并不是游标很糟糕。编译所有打开,关闭和提取的编码都不是增值编程。

答案 1 :(得分:41)

游标使人们过度地将程序性思维模式应用于基于集合的环境。

他们 SLOW !!!

来自SQLTeam

  

请注意游标是   SLOWEST访问SQL内部数据的方法   服务器。应该只在使用时使用   你确实需要访问一行   时间。我能想到的唯一原因   那就是调用存储过程   在每一行。在我发现的Cursor Performance article中   那些游标超过三十次   比基于设定的替代方案慢

答案 2 :(得分:19)

上面有一个答案说“游标是访问SQL Server内部数据的最慢方式...游标比基于集合的替代方案慢30多倍。”

在许多情况下,这种说法可能是正确的,但作为一揽子声明,这是有问题的。例如,在我想要执行更新或删除操作的情况下,我已经很好地利用了游标,这些操作会影响接收常量生产读取的大型表的许多行。运行一次执行这些更新的存储过程最终比基于集合的操作更快,因为基于集合的操作与读取操作冲突并最终导致可怕的锁定问题(并且可能完全终止生产系统,在极端情况下)。

在没有其他数据库活动的情况下,基于集合的操作普遍更快。在生产系统中,它取决于。

答案 3 :(得分:9)

游标倾向于在基于集合的操作更好的地方开始使用SQL开发人员。特别是当人们在学习传统编程语言后学习SQL时,“迭代这些记录”的心态会导致人们不恰当地使用游标。

大多数严肃的SQL书籍都包含一章,禁止使用游标;精心编写的文章清楚地表明游标有它们的位置,但不应该用于基于集合的操作。

很明显,游标是正确的选择,或者至少是正确的选择。

答案 4 :(得分:9)

当使用游标方法时,优化器通常不能使用关系代数来转换问题。通常游标是解决问题的好方法,但SQL是一种声明性语言,数据库中有很多信息,从约束到统计和索引,这意味着优化器有很多选项可以解决问题,而游标几乎明确地指示解决方案。

答案 5 :(得分:8)

在Oracle PL / SQL游标中不会导致表锁,并且可以使用批量收集/批量提取。

在Oracle 10中经常使用的隐式游标

  for x in (select ....) loop
    --do something 
  end loop;

一次隐式提取100行。显式批量收集/批量获取也是可能的。

然而,PL / SQL游标是最后的手段,当您无法解决基于集合的SQL的问题时,请使用它们。

另一个原因是并行化,数据库比逐行命令式代码更容易并行化基于大型集的语句。这也是函数式编程变得越来越流行的原因(Haskell,F#,Lisp,C#LINQ,MapReduce ......),函数式编程使并行化变得更容易。每台计算机的CPU数量正在增加,因此并行化变得越来越成问题。

答案 6 :(得分:6)

通常,因为在关系数据库中,使用游标的代码性能比基于集合的操作差一个数量级。

答案 7 :(得分:6)

上面的答案并未充分强调锁定的重要性。我不是游标的忠实粉丝,因为它们经常导致表级锁定。

答案 8 :(得分:3)

对于它的价值,我已经读过光标将执行其基于集合的对应物的“一个”位置在运行总计中。在一个小表中,按列顺序对行进行求和的速度有利于基于集合的操作,但随着表的行大小的增加,光标将变得更快,因为它可以简单地将运行的总值携带到下一个环。现在 where 你应该做一个不同的参数...

答案 9 :(得分:2)

答案 10 :(得分:2)

在性能(非)问题之外,我认为游标最大的失败是调试很痛苦。特别是与大多数客户端应用程序中的代码相比,其中调试往往相对容易,语言功能往往更容易。实际上,我认为几乎所有人在SQL中使用游标都应该首先在客户端应用程序中进行。

答案 11 :(得分:1)

您可以发布该游标示例或链接到该问题吗?可能比递归CTE更好的方法。

除了其他注释之外,游标使用不当(经常)会导致不必要的页/行锁。

答案 12 :(得分:1)

你可能已经在第二段之后结束了你的问题,而不是仅仅因为他们有不同的观点而把人们称为“疯狂”,而不是试图模仿专业人士,他们可能有充分的理由感受到他们这样做。

关于你的问题,虽然肯定有可能需要调用游标的情况,但根据我的经验,开发人员认为游标“必须”比实际情况更频繁地使用。在我看来,有人在过多使用游标而不使用游标时犯错误的可能性要大得多。

答案 13 :(得分:0)

基本上2块代码执行相同的操作。也许这是一个有点奇怪的例子,但它证明了重点。 SQL Server 2005:

SELECT * INTO #temp FROM master..spt_values
DECLARE @startTime DATETIME

BEGIN TRAN 

SELECT @startTime = GETDATE()
UPDATE #temp
SET number = 0
select DATEDIFF(ms, @startTime, GETDATE())

ROLLBACK 

BEGIN TRAN 
DECLARE @name VARCHAR

DECLARE tempCursor CURSOR
    FOR SELECT name FROM #temp

OPEN tempCursor

FETCH NEXT FROM tempCursor 
INTO @name

SELECT @startTime = GETDATE()
WHILE @@FETCH_STATUS = 0
BEGIN

    UPDATE #temp SET number = 0 WHERE NAME = @name
    FETCH NEXT FROM tempCursor 
    INTO @name

END 
select DATEDIFF(ms, @startTime, GETDATE())
CLOSE tempCursor
DEALLOCATE tempCursor

ROLLBACK 
DROP TABLE #temp

单次更新需要156 ms,光标需要2016 ms。