64

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

4

11 回答 11

105

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

您真正在做的是试图将基于集合的技术强制转换为基于非集合的功能。而且,平心而论,我应该指出游标确实有用,但他们不赞成,因为许多不习惯使用基于集合的解决方案的人使用游标而不是找出基于集合的解决方案。

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

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

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

于 2008-09-12T02:00:00.423 回答
25

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

作为一个非常简单的示例,假设我在一个表中有 100 多条记录,这些记录定义了我想要复制/截断/任何内容的表的名称。哪个最好?硬编码 SQL 来做我需要做的事情?或者遍历这个结果集并使用动态 SQL (sp_executesql) 来执行操作?

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

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

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

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

READ_ONLY 将确保在底层结果集上没有锁。底层结果集的变化将反映在后续的提取中(就像从伪光标中获取 TOP 1 一样)。

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

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

于 2010-06-11T15:53:29.570 回答
17

有一个关于我每次需要时都会使用的游标的解决方法。

我创建了一个表变量,其中包含一个标识列。

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

然后使用计数器变量创建一个 while 块,并使用标识列与计数器匹配的选择语句从表变量中选择我想要的数据。

这样我就不会锁定任何东西并且使用更少的内存和它的安全,我不会因为内存损坏或类似的东西而丢失任何东西。

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

这是一个简单的例子:

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
于 2011-07-20T21:12:24.687 回答
12

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

如果您将它们用于它们的设计目的,我无法找到错误。

于 2008-09-12T01:57:37.260 回答
9

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

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

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

综上所述,在某些情况下光标确实是最好的——它们通常不是初学者想要使用它们的情况。

于 2008-09-12T01:59:55.647 回答
4

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

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

有几种类型的游标(只进、静态、键集、动态)。每一个都有不同的性能特征和相关的开销。确保使用正确的光标类型进行操作。仅转发是默认设置。

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

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

于 2008-09-12T02:04:08.493 回答
4

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

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

于 2008-09-12T02:08:34.333 回答
4

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

DECLARE @commandname NVARCHAR(1000) = '';

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

EXEC sp_executesql @commandname;

只会做你上面所说的。你可以用 Sql 2000 做同样的事情,但是查询的语法会有所不同。

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

伽倻

于 2011-05-19T01:18:06.190 回答
4

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

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

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

SELECT * FROM @temptable WHERE Id=@counter 

或者

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

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

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

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

代替

var items = dataAccess.GetItemsByIds(itemIds);

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

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

于 2015-06-21T12:54:28.620 回答
2

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

避免使用游标可以让 SQL Server 更充分地优化查询性能,这在大型系统中非常重要。

于 2008-09-12T02:01:45.333 回答
1

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

另一方面,内存软件是为单独的操作而设计的,因此循环一组数据并可能对每个项目连续执行不同的操作是它最擅长的。

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

于 2008-09-12T01:59:56.507 回答