A cursor in SQL Server walks through a result one row at a time, the way a loop does in any other language. It is the most natural thing in the world for a programmer to reach for, and it is usually the wrong tool. Here is the measurement that explains why.

What It Looks Like
A cursor has five steps and they are always the same. Declare it, open it, fetch, loop while fetching works, then close and deallocate.
DECLARE @n int = 0, @a money;
DECLARE c CURSOR LOCAL FAST_FORWARD FOR
SELECT amount FROM dbo.Orders WHERE id <= 20000;
OPEN c;
FETCH NEXT FROM c INTO @a;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @n += 1;
FETCH NEXT FROM c INTO @a;
END
CLOSE c;
DEALLOCATE c;That counts 20,000 rows. Here is the same count written as one statement:
SELECT COUNT(*) FROM dbo.Orders WHERE id <= 20000;The Number
I timed both on SQL Server 2025, same table, same 20,000 rows, one after the other:
cursor_rows cursor_ms setbased_rows setbased_ms
20000 260 20000 4260 milliseconds against 4. Sixty five times slower for the same answer, and this is the friendliest cursor I could write. I used LOCAL and FAST_FORWARD, which is the cheapest kind. A default cursor is slower still.
Two things to be fair about. This is one measurement on one machine, and the gap changes with the work inside the loop. What does not change is the shape: the cursor pays a cost per row, and the set based query pays once.
Why the Gap Exists
SQL is a language for describing a result, not for describing steps. You say what you want and the optimizer decides how to get it, including whether to use several processors.
A cursor takes that away. You have told the engine the order to do things in, so it does them in that order, one at a time, and every fetch is its own small piece of work. Twenty thousand rows means twenty thousand of them.
Thinking in Sets
Most cursors people write are a loop that could be one statement. The pattern is nearly always the same: fetch a row, work out a value, update something.
-- what the loop was doing
UPDATE dbo.Orders SET amount = amount * 1.1 WHERE city = 'Dublin';When you catch yourself writing a loop over rows, the question to ask is what the whole set has in common. Usually the answer turns the loop into a WHERE clause, a CASE expression, or a window function.
When a Cursor Is Right
It is not never. Three cases come up in real work.
Each row causes something outside the database. Calling a procedure per row, running a maintenance command per database, sending each row somewhere. There is nothing to set-ify.
Each row genuinely depends on the last. Some running calculations resist set based rewriting, though window functions have removed most of these.
Breaking a huge change into pieces on purpose. Updating fifty million rows in one statement holds locks for a long time and makes the log enormous. Doing it in batches is slower in total and much kinder to everybody else. That is a loop, and it is the right call.
If You Must
Declare it LOCAL so it disappears with the batch. Add FAST_FORWARD for a read-only forward-only walk, which is the cheapest option. Select only the columns you use. Always CLOSE and DEALLOCATE, including in your error handling, because a cursor left open holds resources.
When somebody sends me a slow procedure, a cursor is one of the first things I look for. It is rarely the only problem, and it is usually the easiest one to explain.
A cursor is not a bad habit, it is the right answer to a question you should check you are actually asking.
This post was rewritten from scratch in September 2026. The original, published on 2012-07-31, was a short announcement about something that no longer exists. The address is the same, the subject is now a basic idea worth keeping.
Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.
Discover more from SQL Authority with Pinal Dave
Subscribe to get the latest posts sent to your email.





11 Comments. Leave new
Thanks for the heads up…well explained!!! amazing concept of transaction engine and storage manager..i am now going to forward this to our developers and DBA team.. for sure we are going to give it a try
Hey Pinal, great timing on starting your series. I will in fact be having someone present on nuodb in coming weeks on the Online ColdFusion Meetup (coldfusionmeetup.com). Hope to perhaps join you in spreading the good news on nuodb, for those who would benefit from it. Keep up the great work, as always.
Hi Mr. Pinaldave,
When I run the first command, I got an error.
$ java –jar nuoagent.jar –broker –domain mydomain –password domain_pwd
=> Failed to startup: Address already in use: JVM_Bind
Can you show me how to change the port jvm in nuodb?
Thanks a lot.
Try to look if its already running in the background.
So I looked on the NuoDB website and on their home page they say, WRT database connection support: “ODBC, JDBC, JRuby ActiveRecord, basic PhP, and Hibernate drivers”. Does this mean that .NET applications cannot connect via anything more direct than ODBC? It seems as if that would be pretty inefficient… Or am I missing something WRT .NET connectivity?
@warren: that’s correct.. I hope they’ll release a native .net driver. Not really an option for us until they do..
Warren – In case you have not noticed – ODBC is the way to go protocol to if it can be natively accessed or via oledb for odbc provider. SQL server took that route with native client.
https://blogs.msdn.microsoft.com/sqlnativeclient/2011/08/29/microsoft-is-aligning-with-odbc-for-native-relational-data-access/ –
ODBC is supported for .Net always.
Good news on .NET connectivity – I actually talked to a NuoDB person and he said that they are in fact working on native .NET connectivity; might be a bit to wait but it sounds like it is on their radar.
OK. I have been reading a lot about NuoDB and have even installed it on 2 evaluation servers. I must not have drunk the cool-aid yet, because while I see all of the great press releases and DBA’s saying it is THE way to have an elastically scaleable, ACID database, I find that the product is just not ready for general consumption in a large corporate environment. The documentation is minimal at best and by looking at the list of their recent bug fixes, I would be very reluctant to implement any mission critical systems on this in the near future. I also do not like the present administration platform that relies on 80’s style command prompts. I hope they can make good use of their recent $10 million dollar infusion. If they want to compete with MS SQL Server ( and I really want them to…), they have a lot of work to do.
Hi Alan,
Great points but the product is in Beta and yet the final version to release. I have great faith in them and as far as I know they are about to come up with new enhencements.
I am eagerly waiting as well.
I remain hopeful as well. The impact this architecture could have on disaster recovery alone would be a sea change in the redundant remote hardware model so commonly employed today today.