为什么在SQL Server中使用游标被认为是不好的做法?
我知道SQL 7中有一些性能方面的原因,但SQL Server 2005中仍存在相同的问题吗? 如果在存储过程中有一个结果集,我想单独采取行动,那么游标仍然是一个不好的select? 如果是这样,为什么?
因为游标占用内存并创build锁。
你真正在做的是试图强迫基于集合的技术进入基于非集合的function。 而且,平心而论,我应该指出,游标确实有用处,但是它们被人们所忽视,因为许多不习惯使用基于集合的解决scheme的人使用游标而不是搞清楚基于集合的解决scheme。
但是,当您打开游标时,基本上是将这些行加载到内存中并locking它们,从而创build潜在的块。 然后,在循环游标时,您正在对其他表进行更改,并仍然保持打开游标的所有内存和locking。
所有这些都有可能导致其他用户的性能问题。
所以,作为一般的规则,游标是不屑一顾的。 特别是如果这是解决问题的第一个解决scheme。
以上关于SQL是基于集合的环境的评论都是真实的。 但是,有时候逐行操作是有用的。 考虑元数据和dynamic-sql的组合。
作为一个非常简单的例子,假设我在一个表格中有超过100条logging,这些logging定义了我想要复制/截断的表格的名称。 哪个最好? 对SQL进行硬编码以完成我所需要的操作? 或者遍历这个结果集并使用dynamicSQL(sp_executesql)来执行操作?
使用基于集合的SQL无法实现上述目标。
那么,使用游标还是一个while循环(伪游标)?
只要你使用正确的选项,SQL游标就可以了:
INSENSITIVE将会为您的结果集创build一个临时副本(使您不必为伪游标执行此操作)。
READ_ONLY将确保在底层结果集上没有锁。 底层结果集中的更改将反映在后续的读取中(就像从伪游标中获取TOP 1一样)。
FAST_FORWARD将创build一个优化的只进游标,只读游标。
在把所有游标统治为邪恶之前,先阅读可用的选项。
每当我需要游标时,都会有一些关于游标的工作。
我创build一个带有标识列的表variables。
插入我需要使用的所有数据。
然后使用计数器variables创build一个while块,并使用select语句(标识列匹配计数器)从表variables中select所需的数据。
这样我就不locking任何东西,并使用更less的内存和它的安全,我不会失去任何内存损坏或类似的东西。
而块代码很容易看到和处理。
这是一个简单的例子:
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
我认为游标会得到一个不好的名字,因为SQL新手会发现它们,并想“嘿,循环!我知道如何使用这些! 然后他们继续使用他们的一切。
如果你用它们来devise它们,我不能find它的错。
SQL是一个基于集合的语言 – 这是最好的。
我认为,游戏是一个不错的select,除非你对游戏的了解足够了解他们在有限的情况下的使用。
我不喜欢游标的另一个原因是清晰度。 光标块非常难看,难以清晰有效的使用。
刚才所说的,有些情况下游标真的是最好的 – 通常情况下,初学者并不想使用它们。
有时您需要执行的处理的性质需要使用游标,但出于性能原因,如果可能的话,使用基于集合的逻辑来编写操作总是更好。
我不会称之为使用游标的“不好的做法”,但是它们在服务器上消耗更多的资源(而不是等价的基于集合的方法),而且往往不是这些必要的。 鉴于此,我的build议是在使用游标之前考虑其他选项。
有几种types的游标(只向前,静态,键集,dynamic)。 每个人都有不同的性能特点和相关的开销。 确保您的操作使用正确的光标types。 仅前向是默认的。
使用游标的一个参数是当您需要处理和更新单个行时,特别是对于没有良好唯一键的数据集。 在这种情况下,你可以使用FOR UPDATE子句来声明游标,并使用UPDATE … WHERE CURRENT OF来更新进程。
请注意,“服务器端”游标曾经是stream行的(从ODBC和OLE DB),但ADO.NET不支持它们,AFAIK永远不会。
有很less的情况下使用光标是正确的。 几乎没有任何情况下,它将超越关系,基于集合的查询。 有时程序员更容易用循环来思考,但是使用set逻辑(例如更新表中的大量行)将产生一个解决scheme,它不仅仅是很less的SQL代码行,但运行速度要快得多,通常要快好几个数量级 。
即使Sql Server 2005中的快进游标也无法与基于集合的查询相竞争。 性能下降图通常开始看起来像一个n ^ 2操作相比,基于集,这往往是更线性的数据集增长非常大。
@丹尼尔P – >你不需要使用游标来做到这一点。 你可以很容易地使用集合的理论来做到这一点。 例如:用Sql 2008
DECLARE @commandname NVARCHAR(1000) = ''; SELECT @commandname += 'truncate table ' + tablename + '; '; FROM tableNames; EXEC sp_executesql @commandname;
会简单地做你刚才所说的。 你可以用Sql 2000做同样的事情,但是查询的语法是不同的。
不过,我的build议是尽可能避免游标。
Gayam
游标确实有自己的位置,但我认为这主要是因为它们通常用于单个select语句就足以提供聚合和过滤结果。
避免游标允许SQL Server更充分地优化查询的性能,在较大的系统中非常重要。
游标通常不是疾病,而是一个症状:不使用基于集合的方法(如其他答案中提到的)。
不理解这个问题,只是简单地相信避免“邪恶”光标将解决它,可以使事情变得更糟。
例如,将光标迭代replace为其他迭代代码(例如将数据移动到临时表或表variables),以便以如下方式遍历行:
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选项来泛滥数据库,每个选项往返一次,特别是当对象树/graphics起作用时,臭名昭着的SELECT N + 1问题就会发生。
这是不了解关系数据库和基于集合的方法的应用程序,与使用过程化数据库代码(如T-SQL或PL / SQL)时的游标一样。
我认为,基本问题是数据库是为基于集合的操作而devise和调整的 – 根据数据关系,在一个快速步骤中select,更新和删除大量数据。
另一方面,内存中的软件是针对单个操作而devise的,因此循环遍历一组数据,并可能对每个项目进行串行操作,这是最好的。
循环不是数据库或存储体系结构的devise目的,甚至在SQL Server 2005中,如果您将基本数据设置到自定义程序中并在内存中执行循环,则不会在性能上靠近任何位置,使用尽可能轻量级的数据对象/结构。