Aaron Bertrand

Follow-up on cursor options

September 22, 2015 by in T-SQL Queries | 6 Comments
SentryOne eBooks

In these books, you will find useful, hand-picked articles that will help give insight into some of your most vexing performance problems. These articles were written by several of the SQL Server industry’s leading experts, including Paul White, Paul Randal, Jonathan Kehayias, Erin Stellato, Glenn Berry, Aaron Bertrand, and Joe Sack.

Free Download

Featured Author

Paul Randal, CEO of SQLskills, writes about knee-jerk performance tuning, DBCC, and SQL Server internals.

Paul’s Posts

Note: This post was originally published only in our eBook, High Performance Techniques for SQL Server, Volume 3. You can find out about our eBooks here.

Over three years ago, I wrote a post on cursor options in SQL Server, and why you should override the defaults:

I wanted to post a follow-up to reiterate that – while you should never just accept the defaults – you really should think about which options are most applicable to your scenario. I also wanted to clarify a few items that came up in the comments on that post.

Andrew Kelly brought up a great point, and that is that a STATIC cursor makes a one-time copy of the results, puts them into tempdb, and then avoids any concurrency issues that may impact a DYNAMIC cursor. One option isn't a clear winner over the other in all cases; for example, you may have a lot of cursors (or cursors with very large resultsets) and/or an already over-burdened tempdb and don't want to offload any additional stress there. But it is something worthy of testing.

Fabiano also brought up a great point that both DYNAMIC and FAST_FORWARD cursors can be vulnerable to the Hallowe'en problem (discussed by Paul White in a 4-part series, starting here). Paul also commented that FAST_FORWARD might not be vulnerable to the problem, depending on whether the optimizer chose a static or dynamic plan (Microsoft's Marc Friedman goes into great detail about that here).

Finally, I wanted to point out that not all default cursors are created equal. I ran some tests and checked how SQL Server decided to set cursor options under a variety of scenarios (validated using the sys.dm_exec_cursors dynamic management function). The code is pretty simple:

DECLARE c CURSOR FOR [...blah blah...];
SELECT properties FROM sys.dm_exec_cursors(@@SPID);

Here are the results for the scenarios I tested:

Cursor query is based on… Type Concurrency Scope
a constant (FOR SELECT 1 or FOR SELECT SYSDATETIME()) Snapshot Read Only Global
a #temp / ##temp table Dynamic Optimistic Global
a user table / view Dynamic Optimistic Global
a catalog view / DMV Snapshot Read Only Global
a join #tmp -> user table / view Dynamic Optimistic Global
a join #tmp -> catalog view / DMV Snapshot Read Only Global
a join user table / view -> catalog view / DMV Snapshot Read Only Global

Credit where credit is due – this investigation was triggered by an answer from Jeroen Mostert on Stack Overflow.

So you should be aware that the default options for your cursor, if you don't override them, may be different depending on the query underlying the cursor. If you are expecting a specific behavior in any or all cases, get in the habit of explicitly specifying the options you want.

But really, the point is…

…stop using cursors. There really are very few problems today where the best solution is a cursor, especially if you are on SQL Server 2012 or better – where just about every problem traditionally solved by cursors can be solved using enhancements to window functions. If you still feel you need to use cursors, please take the advice in this post and its predecessor to determine which options you should use.