为什么人们这么讨厌SQL游标?
我可以理解为了避免因为开销和不便而使用游标,但是看起来好像有一些严重的光标恐惧症,在那里人们将竭尽全力避免使用游标。
例如,有一个问题要问如何做一些显而易见的事情,使用recursion自定义函数的普通表expression式(CTE)recursion查询来提供接受的答案,尽pipe这会将可以处理的行数限制为32 (由于sql server中的recursion函数调用限制)。 这对我来说是系统寿命的可怕解决scheme,更不用说为了避免使用简单的游标而付出的巨大努力。
这种疯狂仇恨的原因是什么? 有一些“着名的权威”对游标发布了一个法特瓦? 在光标的核心潜伏着一些无法形容的邪恶,腐败的孩子的道德或什么的?
维基的问题,比代表更感兴趣的答案。
相关信息:
SQL Server快速游标
编辑:让我更精确:我明白, 不应该使用游标,而不是正常的关系操作 ; 这是一个很容易的事情。 我不明白的是,人们为了避免像他们的老鼠那样的游标,甚至在游标是一个更简单和/或更有效的解决scheme的时候,都会避免使用游标。 这是让我感到困惑的非理性仇恨,而不是明显的技术效率。
游标的“开销”仅仅是API的一部分。 游标是RDBMS的一部分如何工作的。 通常CREATE TABLE
和INSERT
有SELECT
语句,实现是明显的内部游标实现。
使用更高级别的“基于集合的运算符”将游标结果捆绑到单个结果集中,这意味着更less的API来回运行。
游标早于提供一stream集合的现代语言。 老C,COBOL,Fortran等等,不得不一次处理一行,因为没有可以广泛使用的“集合”的概念。 Java,C#,Python等,都有一stream的列表结构来包含结果集。
缓慢的问题
在一些圈子里,关系连接是一个谜,人们会编写嵌套的游标而不是简单的连接。 我已经看到了真正的史诗般的嵌套循环操作,写出很多很多的游标。 打败RDBMS优化。 而且运行真的很慢。
简单的SQL重写,用连接代替嵌套的游标循环,一个单一的游标循环可以使程序在100次运行。 [他们以为我是优化的神。 我所做的只是用连接replace嵌套循环。 仍然使用游标。]
这种混乱通常会导致对游标的控告。 但是,它不是游标,这是错误的光标,这是问题。
尺寸问题
对于真正的史诗结果集(即将表转储到文件),游标是必不可less的。 基于集合的操作不能将真正大的结果集作为内存中的单个集合实现。
备择scheme
我尝试尽可能使用ORM层。 但是这有两个目的。 首先,游标由ORM组件pipe理。 其次,将SQL从应用程序分离到configuration文件中。 这不是游标不好。 所有这些打开,closures和提取的代码都不是增值编程。
游标使人们过度地将程序思维应用于基于集合的环境。
他们很慢 !
从SQLTeam :
请注意,游标是访问SQL Server内部数据的最慢的方式。 应该只在真正需要访问一行时使用。 我能想到的唯一原因就是每行调用一个存储过程。 在“ 游标性能”文章中,我发现游标比基于集合的替代选项慢三十倍以上 。
上面有一个答案,“游标是访问SQL Server内部数据的最慢的方法…游标比基于替代选项的速度慢三十倍”。
这种说法在许多情况下可能是正确的,但作为一个总括性的说法是有问题的。 例如,在我想要执行更新或删除操作的情况下,我已经很好地使用了游标,这些操作会影响正在接收恒定生产读取的大表的许多行。 运行一次一行更新的存储过程最终会比基于集合的操作更快,因为基于集合的操作与读取操作相冲突,并最终导致可怕的locking问题(并可能完全中止生产系统,在极端的情况下)。
在没有其他数据库活动的情况下,基于集合的操作普遍更快。 在生产系统中,这取决于。
初级SQL开发人员往往会在集合操作更好的地方使用游标。 特别是当人们学习传统的编程语言学习SQL时,“遍历这些logging”的心态往往会导致人们不适当地使用游标。
最严重的SQL书籍包括禁止使用游标的章节; 写得很好的人清楚地表明游标有自己的位置,但不应该用于基于集合的操作。
显然游戏是正确的select,或者至less是正确的select。
当使用游标方法时,优化程序通常不能使用关系代数来转换问题。 通常游标是解决问题的好方法,但SQL是一种声明性语言,数据库中有很多信息,从约束到统计和索引,这意味着优化器有很多选项可以解决问题,而游标非常明确地指导解决scheme。
上面的答案没有强调locking的重要性。 我不喜欢游标,因为他们经常导致表级锁。
通常,因为在关系数据库上,使用游标的代码的性能比基于集合的操作要差一个数量级。
在Oracle PL / SQL游标中,不会导致表锁,并且可以使用批量收集/批量获取。
在Oracle 10中经常使用的隐式游标
for x in (select ....) loop --do something end loop;
一次提取100行。 显式批量采集/批量提取也是可能的。
但是,PL / SQL游标是不得已而为之的,在无法解决基于集合的SQL的问题时使用它们。
另一个原因是并行化,数据库并行化大型基于集合的语句比逐行命令式代码更容易。 函数式编程变得越来越stream行(Haskell,F#,Lisp,C#LINQ,MapReduce …),函数式编程使并行更容易。 每台计算机的CPU数量正在增加,所以并行化成为越来越多的问题。
为什么值得我读到,“一个”的地方光标将执行其基于对手的是一个总计。 在一张小表上,按列顺序总结行的速度有利于基于集合的操作,但是随着行大小的增加,光标将变得更快,因为它可以简单地将运行的总值传送到循环。 现在,你应该做一个跑步总和是一个不同的论点…
在性能(非)问题之外,我认为最大的游标失败是他们很难debugging。 特别是与大多数客户端应用程序中的代码相比,这些应用程序的debugging往往比较容易,而且语言function更容易。 实际上,我认为几乎所有在SQL中使用游标进行操作的东西都应该首先在客户端应用程序中进行。
你可以张贴光标的例子或链接到问题? 可能比recursionCTE更好。
除了其他注释,游标使用不当(通常是)会导致不必要的页面/行locking。
你可能在第二段之后可能已经总结出了你的问题,而不是简单地说因为他们有一个与你不同的观点而以另外的方式试图嘲笑那些可能有很好的理由去感受他们所做的事情的专业人士。
至于你的问题,虽然肯定会有光标被调用的情况,但根据我的经验,开发人员决定光标“必须”比实际情况更频繁地被使用。 在使用过多游标的时候,有人犯错误的可能性比在我看来要高得多。
基本上2块代码做同样的事情。 也许这有点怪异的例子,但它certificate了这一点。 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毫秒,而光标需要2016毫秒。