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

时间:2008-09-12 01:52:16

标签: sql-server sql-server-2005 cursor

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

11 个答案:

答案 0 :(得分:98)

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

您真正在做的是尝试将基于集合的技术强制转换为基于非集合的功能。并且,公平地说,我应该指出游标有用,但是他们不赞成,因为许多不习惯使用基于集合的解决方案的人使用游标而不是弄清楚集合的解决方案。

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

所有这些都有可能导致其他用户出现性能问题。

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

答案 1 :(得分:21)

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

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

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

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

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

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

READ_ONLY将确保基础结果集上没有锁定。基础结果集的更改将反映在后续提取中(就像从伪游标中获取TOP 1一样)。

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

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

答案 2 :(得分:15)

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

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

插入我需要使用的所有数据。

然后使用计数器变量创建一个while块,并使用select语句从表变量中选择我想要的数据,其中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

答案 3 :(得分:10)

我认为游标的名字很糟糕,因为SQL新手发现它们并且认为“Hey a for a loop!我知道如何使用它们!”然后他们继续将它们用于一切。

如果你将它们用于它们的设计目标,我就不会对它有所了解。

答案 4 :(得分:9)

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

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

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

所有这一切, 某些情况下光标确实是最好的 - 它们通常不是初学者想要使用它们的情况。

答案 5 :(得分:4)

有时,您需要执行的处理的性质需要游标,但出于性能原因,如果可能的话,使用基于集合的逻辑编写操作总是更好。

我不会将它称为“糟糕的做法”来使用游标,但它们确实在服务器上消耗了更多的资源(而不是基于集合的方法),而且通常不需要它们。鉴于此,我的建议是在使用游标之前考虑其他选项。

有几种类型的游标(仅向前,静态,键集,动态)。每个都有不同的性能特征和相关的开销。确保为操作使用正确的光标类型。仅向前是默认值。

使用游标的一个参数是当您需要处理和更新单个行时,尤其是对于没有良好唯一键的数据集。在这种情况下,您可以在声明游标并使用UPDATE ... WHERE CURRENT OF处理更新时使用FOR UPDATE子句。

请注意,“服务器端”游标过去很流行(来自ODBC和OLE DB),但ADO.NET不支持它们,而AFAIK永远不会支持它们。

答案 6 :(得分:4)

@ Daniel P - &gt;你不需要使用游标来做到这一点。您可以轻松使用基于集合的理论来完成它。例如:使用Sql 2008

DECLARE @commandname NVARCHAR(1000) = '';

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

EXEC sp_executesql @commandname;

将完全按照您上面所说的做。你可以用Sql 2000做同样的事情,但查询的语法会有所不同。

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

Gayam

答案 7 :(得分:3)

极少数情况下使用游标是合理的。几乎没有任何情况下它会超越基于集合的关系查询。有时程序员更容易根据循环进行思考,但是使用set逻辑(例如更新表中的大量行)将导致解决方案不仅仅是很少的SQL代码行,但运行得更快,通常几个数量级更快。

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

答案 8 :(得分:2)

游标确实有它们的位置,但我认为这主要是因为它们经常在单个select语句足以提供聚合和结果过滤时使用。

避免游标允许SQL Server更全面地优化查询的性能,这在大型系统中非常重要。

答案 9 :(得分:2)

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

不理解这个问题,只是相信避免“邪恶”。光标会解决它,可能会让事情变得更糟。

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

SELECT * FROM @temptable WHERE Id=@counter 

SELECT TOP 1 * FROM @temptable WHERE Id>@lastId

这种方法,如另一个答案的代码所示,会使事情变得更糟,并且无法解决原始问题。这是一种称为货物崇拜编程的反模式:不知道为什么有些不好,从而实施更糟糕的事情来避免它!我最近将这样的代码(使用#temptable并且没有身份/ PK上的索引)更改回游标,并且稍微更新10000行只需要1秒而不是近3分钟。仍然缺乏基于集合的方法(是较小的邪恶),但我能做到最好的那一刻。

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

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

而不是

var items = dataAccess.GetItemsByIds(itemIds);

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

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

答案 10 :(得分:1)

我认为,基本问题是数据库是为基于集合的操作而设计和调整的 - 根据数据中的关系,在一个快速步骤中选择,更新和删除大量数据。

另一方面,内存中的软件是为单个操作而设计的,因此循环使用一组数据并可能对每个项目进行串行执行不同的操作是最好的。

循环不是数据库或存储体系结构的设计目标,即使在SQL Server 2005中,如果将基本数据集拉到自定义程序中并且执行使用尽可能轻量级的数据对象/结构在内存中循环。