Why is it considered bad practice to use cursors in SQL Server?

Sql ServerSql Server-2005Database Cursor

Sql Server Problem Overview


I knew of some performance reasons back in the SQL 7 days, but do the same issues still exist in SQL Server 2005? If I have a resultset in a stored procedure that I want to act upon individually, are cursors still a bad choice? If so, why?

Sql Server Solutions


Solution 1 - Sql Server

Because cursors take up memory and create locks.

What you are really doing is attempting to force set-based technology into non-set based functionality. And, in all fairness, I should point out that cursors do have a use, but they are frowned upon because many folks who are not used to using set-based solutions use cursors instead of figuring out the set-based solution.

But, when you open a cursor, you are basically loading those rows into memory and locking them, creating potential blocks. Then, as you cycle through the cursor, you are making changes to other tables and still keeping all of the memory and locks of the cursor open.

All of which has the potential to cause performance issues for other users.

So, as a general rule, cursors are frowned upon. Especially if that's the first solution arrived at in solving a problem.

Solution 2 - Sql Server

The above comments about SQL being a set-based environment are all true. However there are times when row-by-row operations are useful. Consider a combination of metadata and dynamic-sql.

As a very simple example, say I have 100+ records in a table that define the names of tables that I want to copy/truncate/whatever. Which is best? Hardcoding the SQL to do what I need to? Or iterate through this resultset and use dynamic-SQL (sp_executesql) to perform the operations?

There is no way to achieve the above objective using set-based SQL.

So, to use cursors or a while loop (pseudo-cursors)?

SQL Cursors are fine as long as you use the correct options:

INSENSITIVE will make a temporary copy of your result set (saving you from having to do this yourself for your pseudo-cursor).

READ_ONLY will make sure no locks are held on the underlying result set. Changes in the underlying result set will be reflected in subsequent fetches (same as if getting TOP 1 from your pseudo-cursor).

FAST_FORWARD will create an optimised forward-only, read-only cursor.

Read about the available options before ruling all cursors as evil.

Solution 3 - Sql Server

There is a work around about cursors that I use every time I need one.

I create a table variable with an identity column in it.

insert all the data i need to work with in it.

Then make a while block with a counter variable and select the data I want from the table variable with a select statement where the identity column matches the counter.

This way i dont lock anything and use alot less memory and its safe, i will not lose anything with a memory corruption or something like that.

And the block code is easy to see and handle.

This is a simple example:

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

Solution 4 - Sql Server

I think cursors get a bad name because SQL newbies discover them and think "Hey a for loop! I know how to use those!" and then they continue to use them for everything.

If you use them for what they're designed for, I can't find fault with that.

Solution 5 - Sql Server

SQL is a set based language--that's what it does best.

I think cursors are still a bad choice unless you understand enough about them to justify their use in limited circumstances.

Another reason I don't like cursors is clarity. The cursor block is so ugly that it's difficult to use in a clear and effective way.

All that having been said, there are some cases where a cursor really is best--they just aren't usually the cases that beginners want to use them for.

Solution 6 - Sql Server

Sometimes the nature of the processing you need to perform requires cursors, though for performance reasons it's always better to write the operation(s) using set-based logic if possible.

I wouldn't call it "bad practice" to use cursors, but they do consume more resources on the server (than an equivalent set-based approach) and more often than not they aren't necessary. Given that, my advice would be to consider other options before resorting to a cursor.

There are several types of cursors (forward-only, static, keyset, dynamic). Each one has different performance characteristics and associated overhead. Make sure you use the correct cursor type for your operation. Forward-only is the default.

One argument for using a cursor is when you need to process and update individual rows, especially for a dataset that doesn't have a good unique key. In that case you can use the FOR UPDATE clause when declaring the cursor and process updates with UPDATE ... WHERE CURRENT OF.

Note that "server-side" cursors used to be popular (from ODBC and OLE DB), but ADO.NET does not support them, and AFAIK never will.

Solution 7 - Sql Server

There are very, very few cases where the use of a cursor is justified. There are almost no cases where it will outperform a relational, set-based query. Sometimes it is easier for a programmer to think in terms of loops, but the use of set logic, for example to update a large number of rows in a table, will result in a solution that is not only many less lines of SQL code, but that runs much faster, often several orders of magnitude faster.

Even the fast forward cursor in Sql Server 2005 can't compete with set-based queries. The graph of performance degradation often starts to look like an n^2 operation compared to set-based, which tends to be more linear as the data set grows very large.

Solution 8 - Sql Server

@ Daniel P -> you don't need to use a cursor to do it. You can easily use set based theory to do it. Eg: with Sql 2008

DECLARE @commandname NVARCHAR(1000) = '';

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

EXEC sp_executesql @commandname;

will simply do what you have said above. And you can do the same with Sql 2000 but the syntax of query would be different.

However, my advice is to avoid cursors as much as possible.

Gayam

Solution 9 - Sql Server

Cursors are usually not the disease, but a symptom of it: not using the set-based approach (as mentioned in the other answers).

Not understanding this problem, and simply believing that avoiding the "evil" cursor will solve it, can make things worse.

For example, replacing cursor iteration by other iterative code, such as moving data to temporary tables or table variables, to loop over the rows in a way like:

SELECT * FROM @temptable WHERE Id=@counter 

or

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

Such an approach, as shown in the code of another answer, makes things much worse and doesn't fix the original problem. It's an anti-pattern called cargo cult programming: not knowing WHY something is bad and thus implementing something worse to avoid it! I recently changed such code (using a #temptable and no index on identity/PK) back to a cursor, and updating slightly more than 10000 rows took only 1 second instead of almost 3 minutes. Still lacking set-based approach (being the lesser evil), but the best I could do that moment.

Another symptom of this lack of understanding can be what I sometimes call "one object disease": database applications which handle single objects through data access layers or object-relational mappers. Typically code like:

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

instead of

var items = dataAccess.GetItemsByIds(itemIds);

The first will usually flood the database with tons of SELECTs, one round trip for each, especially when object trees/graphs come into play and the infamous SELECT N+1 problem strikes.

This is the application side of not understanding relational databases and set based approach, just the same way cursors are when using procedural database code, like T-SQL or PL/SQL!

Solution 10 - Sql Server

Cursors do have their place, however I think it's mainly because they are often used when a single select statement would suffice to provide aggregation and filtering of results.

Avoiding cursors allows SQL Server to more fully optimize the performance of the query, very important in larger systems.

Solution 11 - Sql Server

The basic issue, I think, is that databases are designed and tuned for set-based operations -- selects, updates, and deletes of large amounts of data in a single quick step based on relations in the data.

In-memory software, on the other hand, is designed for individual operations, so looping over a set of data and potentially performing different operations on each item serially is what it is best at.

Looping is not what the database or storage architecture are designed for, and even in SQL Server 2005, you are not going to get performance anywhere close to you get if you pull the basic data set out into a custom program and do the looping in memory, using data objects/structures that are as lightweight as possible.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKilhofferView Question on Stackoverflow
Solution 1 - Sql ServerJosefView Answer on Stackoverflow
Solution 2 - Sql ServerDaniel PView Answer on Stackoverflow
Solution 3 - Sql ServerZorkindView Answer on Stackoverflow
Solution 4 - Sql ServerrpetrichView Answer on Stackoverflow
Solution 5 - Sql ServerMichael HarenView Answer on Stackoverflow
Solution 6 - Sql ServerBrannonView Answer on Stackoverflow
Solution 7 - Sql ServerEric Z BeardView Answer on Stackoverflow
Solution 8 - Sql ServerGayanView Answer on Stackoverflow
Solution 9 - Sql ServerErik HartView Answer on Stackoverflow
Solution 10 - Sql ServerAshView Answer on Stackoverflow
Solution 11 - Sql ServerGuy StarbuckView Answer on Stackoverflow