为什么人们这么讨厌 SQL 游标?

我可以理解因为开销和不便而想避免使用光标的想法,但是看起来有一些严重的光标恐惧症正在发生,人们会不遗余力地避免使用光标。

例如,有一个问题询问如何使用游标完成一些明显琐碎的事情,并提出了使用公共表表达式(CTE)递归查询和递归自定义函数的可接受答案,尽管这将可处理的行数限制为32(由于 sql 服务器中的递归函数调用限制)。这对于系统寿命来说是一个糟糕的解决方案,更不用说为了避免使用简单的光标而付出的巨大努力了。

这种疯狂仇恨的原因是什么?是否有某个“著名权威”对游标发布了追杀令?难道一些难以形容的邪恶潜伏在游标的心脏,腐蚀儿童的道德或东西?

维基百科的问题,比代表更感兴趣的是答案。

相关信息:

快进游标

编辑: 让我更确切地说: 我理解 不应该使用游标来代替普通的关系操作; 这是一个不需要思考的问题。我不明白的是,人们为了避免像虱子一样的游标而不择手段,即使游标是一个更简单和/或更有效的解决方案。让我困惑的是这种非理性的仇恨,而不是显而易见的技术效率。

107888 次浏览

一般来说,因为在关系数据库上,使用游标的代码的性能比基于集合的操作数量级差。

游标使人们过度地将过程思维应用到基于集合的环境中。

他们是 慢点! ! !

来自 SQLTeam:

请注意,游标是 访问 SQL 内部数据的 SLOWEST 方法 服务器。只有在下列情况下才能使用 您确实需要访问 我能想到的唯一原因 就是调用一个存储过程 在“游标”中 性能文章 我发现 光标的强度超过30倍 比基于集合的替代方案更慢

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

大多数严肃的 SQL 书籍都有一章禁止使用游标; 写得好的书明确指出,游标有自己的位置,但不应该用于基于集合的操作。

很明显,在某些情况下,光标是正确的选择,或者至少是正确的选择。

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

你能发布光标示例或链接到问题吗?可能有比递归 CTE 更好的方法。

除了其他注释之外,游标使用不当(这种情况经常发生)还会导致不必要的页/行锁定。

上面的答案是: “游标是访问 SQLServer 内数据的最慢方式... ... 游标比基于集的方法慢30多倍。”

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

在没有其他数据库活动的情况下,基于集合的操作普遍更快。

游标的“开销”只是 API 的一部分。游标是 RDBMS 的各个部分如何在引擎盖下工作的。通常 CREATE TABLEINSERT都有 SELECT语句,实现是显而易见的内部游标实现。

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

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

缓慢的问题

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

简单的 SQL 重写可以将嵌套的游标循环替换为连接,一个单一的平面游标循环可以使程序运行的时间达到100分之一。[他们认为我是优化之神。我所做的只是用连接替换嵌套循环。仍在使用光标。]

这种混乱常常导致对游标的指控。然而,问题不在于光标,而在于对光标的滥用。

规模问题

对于真正宏大的结果集(例如,将表转储到文件中) ,游标是必不可少的。基于集合的操作不能将真正大的结果集具体化为内存中的单个集合。

替代品

我尽量使用 ORM 层。但这有两个目的。首先,游标由 ORM 组件管理。其次,将 SQL 从应用程序分离到一个配置文件中。不是光标不好。而是所有这些打开、关闭和获取的代码都不是增值编程。

基本上两块代码做同样的事情。也许这个例子有点奇怪,但它证明了这一点。SQLServer2005:

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毫秒,而光标需要2016毫秒。

你也许可以在第二段之后结束你的问题,而不是仅仅因为别人和你有不同的观点就说他们“疯了”,或者试图嘲笑专业人士,他们可能有很好的理由去感受他们的方式。

至于你的问题,虽然在某些情况下可能需要调用游标,但根据我的经验,开发人员认为“必须”使用游标的频率远远高于实际情况。在我看来,某人在过多使用光标而不使用它们的时候犯错误的几率要高得多。

值得一提的是,我曾经读到过,光标在执行其基于集合的对应操作时所占的“一个”位置是一个运行总数。对于一个小表,按列对行进行汇总的速度有利于基于集合的操作,但是随着表的行大小增加,光标会变得更快,因为它可以简单地将运行的总值传递到循环的下一次传递。现在 哪里你应该做一个运行总数是一个不同的参数..。

上面的答案没有充分强调锁定的重要性。我不太喜欢游标,因为它们通常会导致表级锁。

在 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 数量正在增加,因此并行化变得越来越成为一个问题。

除了性能(非)问题之外,我认为游标最大的缺点是它们调试起来很痛苦。特别是与大多数客户端应用程序中的代码相比,在这些应用程序中,调试往往相对容易,语言特性往往更容易。事实上,我认为在 SQL 中使用游标所做的几乎所有事情首先都应该发生在客户机应用程序中。