Performance Tuning SQL Server Cursors

Consider using asynchronous cursors if you expect your result set to be very large. This allows you to continue processing while the cursor is still being populated. While it may not actually speed up your application, it should give the appearance to your end users that something is happening sooner that if they have to wait until the entire cursor is populated. [2000, 2005, 2008] Updated 1-29-2009

*****

If you have to use a cursor, break out of the cursor loop as soon as you can. If you find that a problem has occurred, or processing has ended before the full cursor has been processed, then exit immediately. [2000, 2005, 2008] Updated 1-29-2009

*****

If you are using the same cursor more than once in a batch of work, (or within more than one stored procedure), then define the cursor as a global cursor by using the GLOBAL keyword. By not closing or deallocating the cursor until the whole process is finished, a fair amount of time will be saved, as the cursor and the data contained will already be defined, ready for you to use. [2000, 2005, 2008] Updated 1-29-2009

*****

Sometimes, it is handy to be able to perform some calculation on one or more columns of a record, and then take the result of that calculation and then add it to similar calculations performed on other related records to find a grand total.

For example, let’s say you want to find the total dollar cost of an invoice. An invoice will generally involve a header record and one or more detail records. Each detail record will represent a line item on the invoice. In order to calculate the total dollar cost of an invoice, based on two or more line items, you would need to multiply the quantity of each item sold times the price of each item. Then, you would need to add the total price of each line item together in order to get the total dollar cost of the entire invoice. To keep this example simple, let’s ignore things like discounts, taxes, shipping, etc.

One way to accomplish this task would be to use a cursor like the one we see below (we are using the Northwind database for this example code):

DECLARE @LineTotal MONEY      –Declare variables

DECLARE @InvoiceTotal MONEY

SET @LineTotal = 0      –Set variables to 0

SET @InvoiceTotal = 0

DECLARE Line_Item_Cursor CURSOR FOR      –Declare the cursor

SELECT UnitPrice*Quantity      –Multiply unit price times quantity ordered

FROM [order details]

WHERE orderid = 10248      –We are only concerned with invoice 10248

OPEN Line_Item_Cursor      –Open the cursor

FETCH NEXT FROM Line_Item_Cursor INTO @LineTotal      –Fetch next record

WHILE @@FETCH_STATUS = 0

BEGIN

SET @InvoiceTotal = @InvoiceTotal + @LineTotal      –Summarize line items

FETCH NEXT FROM Line_Item_Cursor INTO @LineTotal

END

CLOSE Line_Item_Cursor      –Close cursor

DEALLOCATE Line_Item_Cursor      –Deallocate cursor

SELECT @InvoiceTotal InvoiceTotal      –Display total value of invoice

The result for invoice number 10248 is $440.00.

What the cursor does is to select all of the line items for invoice number 10248, then multiply the quantity ordered times the price to get a line item total, and then it takes each of the line item totals for each record and then adds them all up in order to calculate the total dollar amount for the invoice.

This all works well, but the code is long and hard to read, and performance is not great because a cursor is used. Ideally, for best performance, we need to find another way to accomplish the same goal as above, but without using a cursor.

Instead of using a cursor, let’s rewrite the above code using set-based Transact-SQL instead of a cursor. Here’s what the code looks like:

DECLARE @InvoiceTotal MONEY

SELECT @InvoiceTotal = SUM(UnitPrice*Quantity)

FROM [order details]

WHERE orderid = 10248

SELECT @InvoiceTotal InvoiceTotal

The result for invoice number 10248 is $440.00.

Right away, it is obvious that this is a lot less code and that is it more readable. What may not be obvious is that it uses less server resources and performs faster. In our example — with few rows — the time difference is very small, but if many rows are involved, the time difference between the techniques can be substantial.

The secret here is to use the Transact-SQL “sum” function to summarize the line item totals for you, instead of relying on a cursor. You can use this same technique to help reduce your dependency on using resource-hogging cursors in much of your Transact-SQL code. [2000, 2005, 2008] Updated 1-29-2009

]]>

Leave a comment

Your email address will not be published.