使用 SQL 2005 / 2008
我必须使用前向光标,但我不想表现不佳。有没有更快的方法可以在不使用游标的情况下循环?
使用 SQL 2005 / 2008
我必须使用前向光标,但我不想表现不佳。有没有更快的方法可以在不使用游标的情况下循环?
这是使用游标的示例:
DECLARE @VisitorID int
DECLARE @FirstName varchar(30), @LastName varchar(30)
-- declare cursor called ActiveVisitorCursor
DECLARE ActiveVisitorCursor Cursor FOR
SELECT VisitorID, FirstName, LastName
FROM Visitors
WHERE Active = 1
-- Open the cursor
OPEN ActiveVisitorCursor
-- Fetch the first row of the cursor and assign its values into variables
FETCH NEXT FROM ActiveVisitorCursor INTO @VisitorID, @FirstName, @LastName
-- perform action whilst a row was found
WHILE @@FETCH_STATUS = 0
BEGIN
Exec MyCallingStoredProc @VisitorID, @Forename, @Surname
-- get next row of cursor
FETCH NEXT FROM ActiveVisitorCursor INTO @VisitorID, @FirstName, @LastName
END
-- Close the cursor to release locks
CLOSE ActiveVisitorCursor
-- Free memory used by cursor
DEALLOCATE ActiveVisitorCursor
现在这里是示例如何在不使用游标的情况下获得相同的结果:
/* Here is alternative approach */
-- Create a temporary table, note the IDENTITY
-- column that will be used to loop through
-- the rows of this table
CREATE TABLE #ActiveVisitors (
RowID int IDENTITY(1, 1),
VisitorID int,
FirstName varchar(30),
LastName varchar(30)
)
DECLARE @NumberRecords int, @RowCounter int
DECLARE @VisitorID int, @FirstName varchar(30), @LastName varchar(30)
-- Insert the resultset we want to loop through
-- into the temporary table
INSERT INTO #ActiveVisitors (VisitorID, FirstName, LastName)
SELECT VisitorID, FirstName, LastName
FROM Visitors
WHERE Active = 1
-- Get the number of records in the temporary table
SET @NumberRecords = @@RowCount
--You can use: SET @NumberRecords = SELECT COUNT(*) FROM #ActiveVisitors
SET @RowCounter = 1
-- loop through all records in the temporary table
-- using the WHILE loop construct
WHILE @RowCounter <= @NumberRecords
BEGIN
SELECT @VisitorID = VisitorID, @FirstName = FirstName, @LastName = LastName
FROM #ActiveVisitors
WHERE RowID = @RowCounter
EXEC MyCallingStoredProc @VisitorID, @FirstName, @LastName
SET @RowCounter = @RowCounter + 1
END
-- drop the temporary table
DROP TABLE #ActiveVisitors
“从不使用游标”是一个很好的例子,说明了简单规则的破坏性。是的,它们很容易沟通,但是当我们去掉规则的原因,以便我们有一个“易于遵循”的规则时,大多数人只会盲目地遵循规则而不考虑它,即使遵循规则已经负面影响。
游标,至少在 SQL Server / T-SQL 中,被极大地误解了。说“游标影响 SQL 的性能”是不准确的。他们当然有这种倾向,但这在很大程度上与人们如何使用它们有关。如果使用得当,游标比循环更快、更高效、更不容易出错WHILE
(是的,这是真的,并且已经被一遍又一遍地证明,不管谁认为“游标是邪恶的”)。
第一种选择是尝试找到一种基于集合的方法来解决问题。
如果逻辑上没有基于集合的方法(例如,需要对EXEC
每一行调用),并且对游标的查询正在命中真实(非临时)表,则使用将语句结果放入内部的STATIC
关键字SELECT
临时表,因此在您遍历结果时不会锁定查询的基表。默认情况下,游标对查询的基础表中的更改“敏感”,并将验证这些记录在您调用时是否仍然存在FETCH NEXT
(因此游标通常被视为缓慢的原因的很大一部分)。如果您需要对在处理结果集时可能会消失的记录保持敏感,则使用STATIC
将无济于事,但如果您正在考虑转换为WHILE
针对临时表循环(因为它也不知道基础数据的更改)。
如果游标的查询仅从临时表和/或表变量中进行选择,那么您不需要阻止锁定,因为在这些情况下您没有并发问题,在这种情况下您应该使用FAST_FORWARD
而不是STATIC
.
我认为指定 的三个选项也有帮助LOCAL READ_ONLY FORWARD_ONLY
,除非您特别需要一个不是其中一个或多个的游标。但我还没有测试过它们是否能提高性能。
假设操作不符合基于集合的条件,那么以下选项对于大多数操作来说是一个很好的起点:
DECLARE [Thing1] CURSOR LOCAL READ_ONLY FORWARD_ONLY STATIC
FOR SELECT columns
FROM Schema.ReadTable(s);
DECLARE [Thing2] CURSOR LOCAL READ_ONLY FORWARD_ONLY FAST_FORWARD
FOR SELECT columns
FROM #TempTable(s) and/or @TableVariables;
您可以执行WHILE
循环,但是您应该寻求实现更多基于集合的操作,因为 SQL 中的任何迭代都会受到性能问题的影响。
正如@Neil 建议的那样,Common Table Expressions 将是一个不错的选择。这是 Adventureworks 的一个示例:
WITH cte_PO AS
(
SELECT [LineTotal]
,[ModifiedDate]
FROM [AdventureWorks].[Purchasing].[PurchaseOrderDetail]
),
minmax AS
(
SELECT MIN([LineTotal]) as DayMin
,MAX([LineTotal]) as DayMax
,[ModifiedDate]
FROM cte_PO
GROUP BY [ModifiedDate]
)
SELECT * FROM minmax ORDER BY ModifiedDate
这是它返回的前几行:
DayMin DayMax ModifiedDate
135.36 8847.30 2001-05-24 00:00:00.000
129.8115 25334.925 2001-06-07 00:00:00.000
我必须使用前向光标,但我不想表现不佳。有没有更快的方法可以在不使用游标的情况下循环?
这取决于您对光标的操作。
几乎所有东西都可以使用基于集合的操作重写,在这种情况下,循环在查询计划内执行,因为它们不涉及上下文切换,所以速度要快得多。
但是,有些事情SQL Server
并不擅长,例如计算累积值或加入日期范围。
使用以下命令可以更快地进行这些类型的查询CURSOR
:
但同样,这是一个非常罕见的例外,通常基于集合的方式表现更好。
如果您发布了查询,我们可能会对其进行优化并删除CURSOR
.
根据您的需要,您可以使用计数表。
Jeff Moden 有一篇关于计数表的优秀文章在这里
不要使用游标,而是寻找基于集合的解决方案。如果您找不到基于集合的解决方案......仍然不要使用光标!发布您要实现的目标的详细信息,有人将能够为您找到基于集合的解决方案。
可能在某些情况下可以使用Tally 表。它可能是循环和游标的一个很好的替代方案,但请记住它不能适用于所有情况。可以在这里找到一个很好的解释案例