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
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 (
|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.