为什么在SQL Server中使用游标被认为是不好的做法?

Kil*_*fer 58 sql-server sql-server-2005 cursor

我在SQL 7天后就知道了一些性能原因,但SQL Server 2005中是否仍然存在相同的问题?如果我在存储过程中有一个我想单独操作的结果集,那么游标仍然是一个糟糕的选择吗?如果是这样,为什么?

Jos*_*sef 99

因为游标占用内存并创建锁.

您真正在做的是尝试将基于集合的技术强制转换为基于非集合的功能.并且,公平地说,我应该指出游标确实有用,但是它们不受欢迎,因为许多不习惯使用基于集合的解决方案的人使用游标而不是找出基于集合的解决方案.

但是,当您打开游标时,您基本上将这些行加载到内存中并锁定它们,从而创建潜在的块.然后,当您循环光标时,您正在更改其他表并仍然保持光标的所有内存和锁定打开.

所有这些都有可能导致其他用户的性能问题.

因此,作为一般规则,游标不受欢迎.特别是如果这是解决问题的第一个解决方案.

  • Joe Barone:迟了3年......但如果你绝对必须遍历结果集,你应该*使用光标.事实上,使用表变量**和**只读,只转发游标是一个很好的组合:一个可以快速迭代所有记录,而不用担心锁.喜欢这个家伙(只看激活程序):http://blogs.msdn.com/b/sql_service_broker/archive/2008/07/25/reusing-dialogs-with-a-dialog-pool.aspx (7认同)
  • 我同意约瑟夫的观点.要添加他的注释,如果绝对必须遍历结果集,则Cursors的一个很好的替代方法是使用表变量并使用WHILE循环遍历记录.我发现这个替代方案可以获得游标的所有好处而没有缺点. (4认同)

Dan*_*l P 22

以上关于SQL是基于集合的环境的评论都是正确的.但是,有时候逐行操作很有用.考虑元数据和dynamic-sql的组合.

作为一个非常简单的例子,假设我在表中有100多条记录,用于定义要复制/截断/删除的表的名称.哪个最好?硬编码SQL来做我需要的东西?或者遍历此结果集并使用dynamic-SQL(sp_executesql)来执行操作?

使用基于集合的SQL无法实现上述目标.

那么,使用游标还是while循环(伪游标)?

只要您使用正确的选项,SQL游标就可以了:

INSENSITIVE将为您的结果集创建一个临时副本(使您不必自己为伪游标执行此操作).

READ_ONLY将确保底层结果集上没有锁定.基础结果集的更改将反映在后续提取中(与从伪游标获取TOP 1相同).

FAST_FORWARD将创建一个优化的只进,只读游标.

在将所有游标统治为邪恶之前,请阅读可用选项.

  • @ Paul-SebastianManole hmm [文档阅读](https://msdn.microsoft.com/en-us/library/ms180169.aspx?f=255&MSPPError=-2147217396)`FAST_FORWARD指定FORWARD_ONLY,READ_ONLY游标`为什么你不相信? (2认同)

Zor*_*ind 15

我每次需要游标时都会使用游标.

我创建了一个带有标识列的表变量.

插入我需要处理的所有数据.

然后使用计数器变量创建一个while块,并使用select语句从table变量中选择我想要的数据,其中identity列与计数器匹配.

这种方式我不会锁定任何东西,并使用更少的内存和它的安全,我不会丢失任何内存损坏或类似的东西.

并且块代码易于查看和处理.

这是一个简单的例子:

DECLARE @TAB TABLE(ID INT IDENTITY, COLUMN1 VARCHAR(10), COLUMN2 VARCHAR(10))

DECLARE @COUNT INT,
        @MAX INT, 
        @CONCAT VARCHAR(MAX), 
        @COLUMN1 VARCHAR(10), 
        @COLUMN2 VARCHAR(10)

SET @COUNT = 1

INSERT INTO @TAB VALUES('TE1S', 'TE21')
INSERT INTO @TAB VALUES('TE1S', 'TE22')
INSERT INTO @TAB VALUES('TE1S', 'TE23')
INSERT INTO @TAB VALUES('TE1S', 'TE24')
INSERT INTO @TAB VALUES('TE1S', 'TE25')

SELECT @MAX = @@IDENTITY

WHILE @COUNT <= @MAX BEGIN
    SELECT @COLUMN1 = COLUMN1, @COLUMN2 = COLUMN2 FROM @TAB WHERE ID = @COUNT

    IF @CONCAT IS NULL BEGIN
        SET @CONCAT = '' 
    END ELSE BEGIN 
        SET @CONCAT = @CONCAT + ',' 
    END

    SET @CONCAT = @CONCAT + @COLUMN1 + @COLUMN2

    SET @COUNT = @COUNT + 1
END

SELECT @CONCAT
Run Code Online (Sandbox Code Playgroud)

  • 对不起,这是一种称为货物狂热编程的反模式:不知道为什么游标被认为是坏的,你实施更糟糕的事情!想象一下,你无法在基于集合的查询中连接字符串(例如,在SQL Server中使用COALESCE),那么光标将是首选方法.将内容移动到临时表或表变量并为每个下一行执行SELECT会增加很大的开销.如果身份/ PK没有索引,那就更糟了. (5认同)
  • 我将假设你理解连接只是一个例子.基于测试,当我不得不在sql server 2005左右使用这个方法时,它比光标更快.我知道为什么游标被认为是坏的.我发现你的预先判断或任何你的意图与这个大写的为什么,是不必要的. (5认同)

rpe*_*ich 10

我认为游标的名字很糟糕,因为SQL新手发现它们并且认为"嘿,因为循环!我知道如何使用它们!" 然后他们继续将它们用于一切.

如果你将它们用于它们的设计目标,我就不会错.


Mic*_*ren 9

SQL是一种基于集合的语言 - 这是它最擅长的.

我认为游标仍然是一个糟糕的选择,除非你对它们有足够的了解,以证明它们在有限的情况下使用它们.

我不喜欢游标的另一个原因是清晰度.光标块太丑了,很难以清晰有效的方式使用.

所有这一切已经表示,目前某些情况下,光标真的是最好的-他们只是通常不是初学者想使用它们的情况.


Eri*_*art 8

游标通常不是疾病,而是它的症状:不使用基于集合的方法(如其他答案中提到的)。

不理解这个问题,并且简单地相信避免“邪恶”光标就能解决它,可能会让事情变得更糟。

例如,用其他迭代代码替换游标迭代,例如将数据移动到临时表或表变量,以如下方式循环遍历行:

SELECT * FROM @temptable WHERE Id=@counter 
Run Code Online (Sandbox Code Playgroud)

或者

SELECT TOP 1 * FROM @temptable WHERE Id>@lastId
Run Code Online (Sandbox Code Playgroud)

这种方法,如另一个答案的代码所示,会使事情变得更糟,并且不能解决原始问题。这是一种称为货物邪教编程的反模式:不知道为什么某些东西是坏的,因此实施更糟糕的东西来避免它!我最近将此类代码(使用 #temptable 并且在身份/PK 上没有索引)更改回游标,并且更新略多于 10000 行只花费了 1 秒而不是近 3 分钟。仍然缺乏基于集合的方法(两害相权取其轻),但那一刻我能做到的最好的。

这种缺乏理解的另一个症状可能是我有时所说的“单一对象疾病”:通过数据访问层或对象关系映射器处理单个对象的数据库应用程序。通常代码如下:

var items = new List<Item>();
foreach(int oneId in itemIds)
{
    items.Add(dataAccess.GetItemById(oneId);
}
Run Code Online (Sandbox Code Playgroud)

代替

var items = dataAccess.GetItemsByIds(itemIds);
Run Code Online (Sandbox Code Playgroud)

第一个通常会用大量的 SELECT 淹没数据库,每个 SELECT 一次往返,特别是当对象树/图发挥作用并且臭名昭著的 SELECT N+1 问题出现时。

这是不理解关系数据库和基于集合的方法的应用程序方面,就像使用过程数据库代码(如 T-SQL 或 PL/SQL)时游标的方式一样!


Eri*_*ard 5

在非常非常少的情况下,使用游标是合理的。几乎在任何情况下它都会优于基于集合的关系查询。有时,程序员用循环来思考会更容易,但是使用集合逻辑(例如更新表中的大量行)将导致解决方案不仅减少了 SQL 代码行,但运行速度要快得多,通常快几个数量级

即使是 Sql Server 2005 中的快进游标也无法与基于集合的查询竞争。与基于集合的操作相比,性能下降的图表通常开始看起来像 n^2 操作,随着数据集变得非常大,它往往更加线性。


小智 5

@ Daniel P -> 你不需要使用光标来做到这一点。您可以轻松地使用基于集合的理论来做到这一点。例如:使用 Sql 2008

DECLARE @commandname NVARCHAR(1000) = '';

SELECT @commandname += 'truncate table ' + tablename + '; ';
FROM tableNames;

EXEC sp_executesql @commandname;
Run Code Online (Sandbox Code Playgroud)

将简单地执行您上面所说的操作。您可以对 Sql 2000 执行相同的操作,但查询的语法会有所不同。

不过,我的建议是尽可能避免使用游标。

迦耶姆