goo*_*yui 7 t-sql sql-server sql-server-2005 sql-server-2008 database-cursor
使用SQL 2005/2008
我必须使用前向光标,但我不想遭受糟糕的性能.有没有更快的方法我可以循环而不使用游标?
Sol*_*zky 10
"NEVER use Cursors" is a wonderful example of how damaging simple rules can be. Yes, they are easy to communicate, but when we remove the reason for the rule so that we can have an "easy to follow" rule, then most people will just blindly follow the rule without thinking about it, even if following the rule has a negative impact.
Cursors, at least in SQL Server / T-SQL, are greatly misunderstood. It is not accurate to say "Cursors affect performance of SQL". They certainly have a tendency to, but a lot of that has to do with how people use them. When used properly, Cursors are faster, more efficient, and less error-prone than WHILE
loops (yes, this is true and has been proven over and over again, regardless of who argues "cursors are evil").
First option is to try to find a set-based approach to the problem.
If logically there is no set-based approach (e.g. needing to call EXEC
per each row), and the query for the Cursor is hitting real (non-Temp) Tables, then use the STATIC
keyword which will put the results of the SELECT
statement into an internal Temporary Table, and hence will not lock the base-tables of the query as you iterate through the results. By default, Cursors are "sensitive" to changes in the underlying Tables of the query and will verify that those records still exist as you call FETCH NEXT
(hence a large part of why Cursors are often viewed as being slow). Using STATIC
will not help if you need to be sensitive of records that might disappear while processing the result set, but that is a moot point if you are considering converting to a WHILE
loop against a Temp Table (since that will also not know of changes to underlying data).
If the query for the cursor is only selecting from temporary tables and/or table variables, then you don't need to prevent locking as you don't have concurrency issues in those cases, in which case you should use FAST_FORWARD
instead of STATIC
.
I think it also helps to specify the three options of LOCAL READ_ONLY FORWARD_ONLY
, unless you specifically need a cursor that is not one or more of those. But I have not tested them to see if they improve performance.
假设该操作不符合基于集合的条件,那么以下选项是大多数操作的良好起点:
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;
Run Code Online (Sandbox Code Playgroud)
您可以进行WHILE
循环,但是您应该寻求实现基于集合的更多操作,因为SQL中的任何迭代操作都可能会遇到性能问题。
http://msdn.microsoft.com/en-us/library/ms178642.aspx
这是使用游标的示例:
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
Run Code Online (Sandbox Code Playgroud)
现在是示例,我们如何不使用游标就可以得到相同的结果:
/* 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
Run Code Online (Sandbox Code Playgroud)