0

I was optimizing a stored procedure written in cursors (DECLARE cur_name CURSOR...

The client saw this SP ran for more than a day so, my manager assigned this to me.

Upon searching, I used table variable and a while loop to iterate each record.

To my surprise, the difference between the two was huge.

Someone told me that while loop is essentially, a CURSOR.

But when I used table variable with a WHILE LOOP, it ran very very very fast.

Now, what do cursors do internally?

9
  • 1
    This post is good to know about Internal architecture of cursors
    – vijayp
    Commented Aug 28, 2014 at 8:26
  • 2
    The default options for cursors in SQL Server are expensive and heavy-handed. See this post. Also your WHILE loop was still a cursor and I guarantee you can get your cursor to operate at the same speed. See this post. Commented Aug 28, 2014 at 10:47
  • If you could add it to your question, it would be nice to see the code for the SP that implements the cursor, and for the one that does not.
    – Hannah Vernon
    Commented Aug 28, 2014 at 22:34
  • @MaxVernon - The SP is quite hard to simplify (for example's sake)... But it was implemented as "DECLARE cur_name CURSOR FAST_FORWARD FOR <select statement here>
    – Jack Frost
    Commented Aug 29, 2014 at 2:24
  • A while loop is not always exactly the same as a cursor. A cursor always accesses one row at a time whereas a sql statement within a while loop can potentially still be set based, for instance all rows where level=1 then all rows where level=2 etc.
    – Steve
    Commented Aug 29, 2014 at 14:17

2 Answers 2

5

Cursors are loops, but they can be more complex for the database engine to implement depending on the options you chose.

There are many options for cursors in TSQL that can have a significant impact on performance depending on the SELECT that feeds the cursor with information (see the TSQL docs for detail). The options will change what locks and other resources are needed, could cause parts of the query to be rerun, and so forth. Also your inner loop may be doing things that affect the behaviour of the cursor by updating base tables it reads from in a way that makes the database engine need to do more work.

If we could see the two bits of code you are talking about we could be more specific.

By running the initial query into a table variable and looping through that you are essentially emulating a STATIC FAST_FORWARD READ_ONLY cursor, those options remove quite a chunk of the complexity that the engine might otherwise have to worry about. Your main SELECT is definitely run once and in one go, and your loop is not competing with other activity on the base data while it interacts with the copy in the variable.

Of course you might also find that something within your two loops (the one using the cursor and the one without) has significantly different performance metrics if they are not written exactly the same.

5
  • I literally changed cursor to table variable. Nothing more. Also, the cursor already has a FAST_FORWARD in it. Btw, what do you mean by "they(cursors) can be more COMPLEX for the database engine to implement depending on the options you chose"...? What are those complexes?
    – Jack Frost
    Commented Aug 28, 2014 at 8:49
  • 1
    The two can't be identical. You need to use FETCH NEXT with a cursor and can't read data that way from a table variable (unless you are using a cursor to iterate that?). The extra complexities are maintaining consistency and reducing contention with other processes. If the cursor is STATIC you have a snapshot of the data and it doesn't have to worry about you (or other processes) updating the data during the life of the cursor, and so forth. Commented Aug 28, 2014 at 9:13
  • Ah... I see. I designed my table variable with a ROWCNT int IDENTITY(1,1) PRIMARY KEY. I use it to "fetch" for the next record (select * from foo where ROWCNT = @counter).
    – Jack Frost
    Commented Aug 28, 2014 at 10:19
  • When cursor is set to static, does that mean it will hold the result set just like a temp table would?
    – Jack Frost
    Commented Aug 28, 2014 at 10:20
  • Essentially yes, a static cursor will spool the full resultset into tempdb and iterate if from there. That doesn't mean the data ends up on disk (for large sets it will, but that is true of table variables too). Commented Aug 28, 2014 at 10:34
3

Your solution, although a WHILEloop, is essentially still a cursor in the sense that you loop over a number of records and execute one or more statements once for each record. It's not the WHILE loop in itself, but rather the coding pattern where you loop over rows in a table and execute a statement for each row. Typically, "traditional" programming languages are row-based - this is the main difference between SQL and, for instance, C#, VBA, etc.

Why your query performs better with table variables may depend on a number of potential factors that we can't deduct from your question.

What you want to do when you're eliminating cursor-based solutions is to turn them into set-based solutions. Here's a very primitive pseudo-example:

DECLARE @sum int, @val int;

DECLARE cur CURSOR FOR SELECT val FROM table;
OPEN cur;

FETCH NEXT FROM cur INTO @val;
WHILE (@@FETCH_STATUS=0) BEGIN;
    SET @sum=@sum+@val;
    FETCH NEXT FROM cur INTO @val;
END;

CLOSE cur;
DEALLOCATE cur;

SELECT @sum AS grantTotal;

.. is equivalent to the following set-based query:

SELECT SUM(val) AS grandTotal FROM table;

In the first query, we loop over a table, row-by-row. In the second query, the entire calculation is done with the entire table as a single "set", i.e. set-based. Obviously, most cursor-based patterns will be a lot more complex, particularly if they launch a stored procedure for each row in the cursor. There isn't a single trick for how to convert a cursor-based solution to set-based one.

1
  • Hi and thank you for the reply. Reason why I didn't turn the cursors into a set based is because inside the loop, there are many conditions and if met, it will execute another SP then pass respective parameters...
    – Jack Frost
    Commented Aug 28, 2014 at 8:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.