January 15, 2007 at 10:58 am
hi guys what are cursors? why are they so bad? and is there a way to check on what cursors i have in my sql server databases?
thanks!
jessica
January 15, 2007 at 11:49 am
Because cursors indicate a row-by-row (if this row contains ... then I'm not interested) logic whilst sql server is optimized for set-based logic (give me all that have the value ...).
January 15, 2007 at 12:19 pm
The real reason behind this is that most of the time 95%+, then cursor based approach is gonna be slower, sometimes exponentially slower (if cursor inside a cursor) than the equivalent set based query.
This statement is often made to "scare"/force someone into trying to find the appropriate set based solution. This is so because newbies to sql server try to emulate what they have learnt to do in vb or c# and process row by row in a while. While there's theorically nothing wrong with that approach, it's one of the worst ways to work in sql server. So that's why we generally tell someone to avoid it. However there are quite a few exceptions, especially when dealing in administration tasks. Such as, for each table, in each database in each server, do this process...
There's just no way to avoid a cursor (well any row by row construct) in this case.
January 16, 2007 at 4:17 am
As to the question about checking, you could write a cursor that would check all databases on the server for cursors :-).
Problem is, that some cursors are "hidden" to such check, because they are part of the application which uses the database - not part of the database itself - so you don't find all. To check the database, you'd need to look into stored procedures, user defined functions and triggers. These are all stored in syscomments table, in column text:
SELECT so.[name], so.xtype, sc.[text]
FROM syscomments sc
JOIN sysobjects so ON so.[id]=sc.[id]
WHERE sc.[text] LIKE '%cursor%'
Read something about the system tables and find out, what more can you get from them... this is just a basic outline. And one warning: this is not guaranteed to bring 100% of occurences, since longer procedure can be stored in several rows of syscomments table, with part of the searched word in one row and rest in another. But in your case, it should work well.
January 17, 2007 at 9:00 am
thank you all of you!!!!
January 19, 2007 at 5:29 pm
not exponentially slower, only power law slower.
---------------------------------------
elsasoft.org
January 21, 2007 at 2:59 pm
One of the other problems with Cursors AND While-Loops is the number of resources (mostly cpu time) they use when compared to properly written setbased code... the following example code is written to use and compare a Cursor, a While-Loop, and Setbased code. The code doesn't really do anything useful... All each "module" does is measure what it takes to read a given number of rows into a variable using each of the 3 methods. Here's the output from a run on my home machine (SQL Server 2000 sp3a Developer Edition, 1.8Ghz Pentium, 2Gig Ram, 80 gig IDE HD).
(100000 row(s) affected)
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
TestType TotalDurMS TotalCPU TotalIO
------------- ----------- ----------- --------------------
Cursor totals 2733 2344 10
(1 row(s) affected)
(100000 row(s) affected)
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
TestType TotalDurMS TotalCPU TotalIO
----------------- ----------- ----------- --------------------
While Loop totals 2326 1968 10
(1 row(s) affected)
(100000 row(s) affected)
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
TestType TotalDurMS TotalCPU TotalIO
---------------- ----------- ----------- --------------------
Set Based totals 93 78 10
(1 row(s) affected)
...and, here's the code...
--===== Declare local variables
DECLARE @TestSizeRows INT
SET @TestSizeRows = 100000
DECLARE @StartTime DATETIME
DECLARE @CPU INT
DECLARE @io INT
DECLARE @Bitbucket INT
--\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
--===== Create the CURSOR test table in a database we know everyone has
USE TEMPDB
IF OBJECT_ID('dbo.CursorTest') IS NOT NULL
DROP TABLE CursorTest
SET ROWCOUNT @TestSizeRows
SELECT IDENTITY(INT,1,1) AS SomeNumber
INTO dbo.CursorTest
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
SET ROWCOUNT 0
--===== Every table should have a primary key
ALTER TABLE dbo.CursorTest
ADD CONSTRAINT PK_CursorTest_SomeNumber
PRIMARY KEY CLUSTERED (SomeNumber)
--=============================================================================
-- Cursor solution for loading a variable with 100,000 numbers.
-- Previous values in the variable are simply discarded to prevent display
-- or update IO from tainting the times.
--=============================================================================
--===== Clear cache and reset buffers
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
--===== Get starting resource values
SELECT @StartTime = GETDATE(),
@CPU = SUM(CPU),
@io = SUM(Physical_IO)
FROM Master.dbo.SysProcesses
WHERE SPID = @@SPID
--===== Run the cursor solution
DECLARE TestCursor CURSOR FAST_FORWARD
FOR
SELECT SomeNumber
FROM CursorTest
ORDER BY SomeNumber
OPEN TestCursor
-- Perform the required first fetch
FETCH NEXT FROM TestCursor INTO @Bitbucket
-- Check @@FETCH_STATUS to see if there are any more rows to fetch.
WHILE @@FETCH_STATUS = 0
BEGIN
-- This is executed as long as the previous fetch succeeds.
FETCH NEXT FROM TestCursor INTO @Bitbucket
END
-- Perform the required "housecleaning" to prevent "memory leaks", etc.
CLOSE TestCursor
DEALLOCATE TestCursor
--===== Get ending resource values and calc totals
SELECT 'Cursor totals' AS TestType,
DATEDIFF(ms,@StartTime,GETDATE()) AS TotalDurMS,
SUM(CPU) - @CPU AS TotalCPU,
SUM(Physical_IO) - @io AS TotalIO
FROM Master.dbo.SysProcesses
WHERE SPID = @@SPID
--\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
--===== Create the WHILE LOOP Test table in a database we know everyone has
USE TEMPDB
IF OBJECT_ID''dbo.WhileTes'') IS NOT NULL
DROP TABLE WhileTest
SET ROWCOUNT @TestSizeRows
SELECT IDENTITY(INT,1,1) AS SomeNumber
INTO dbo.WhileTest
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
SET ROWCOUNT 0
--===== Every table should have a primary key
ALTER TABLE dbo.WhileTest
ADD CONSTRAINT PK_WhileTest_SomeNumber
PRIMARY KEY CLUSTERED (SomeNumber)
--=============================================================================
-- WHILE loop solution for loading a variable with 100,000 numbers.
-- Previous values in the variable are simply discarded to prevent display
-- or update IO from tainting the times.
--=============================================================================
--===== Clear cache and reset buffers
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
--===== Declare Local variables
DECLARE @Counter INT
SET @Counter = 1
--===== Get starting resource values
SELECT @StartTime = GETDATE(),
@CPU = SUM(CPU),
@io = SUM(Physical_IO)
FROM Master.dbo.SysProcesses
WHERE SPID = @@SPID
WHILE @Counter <= @TestSizeRows
BEGIN
SELECT @Bitbucket = SomeNumber
FROM WhileTest
WHERE SomeNumber = @Counter
SET @Counter = @Counter + 1
END
--===== Get ending resource values and calc totals
SELECT''While Loop total'' AS TestType,
DATEDIFF(ms,@StartTime,GETDATE()) AS TotalDurMS,
SUM(CPU) - @CPU AS TotalCPU,
SUM(Physical_IO) - @io AS TotalIO
FROM Master.dbo.SysProcesses
WHERE SPID = @@SPID
--\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
--===== Create the SET BASED test table in a database we know everyone has
USE TEMPDB
IF OBJECT_I(''dbo.SetBaseTet'') IS NOT NULL
DROP TABLE SetBaseTest
SET ROWCOUNT @TestSizeRows
SELECT IDENTITY(INT,1,1) AS SomeNumber
INTO dbo.SetBaseTest
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
SET ROWCOUNT 0
--===== Every table should have a primary key
ALTER TABLE dbo.SetBaseTest
ADD CONSTRAINT PK_SetBaseTest_SomeNumber
PRIMARY KEY CLUSTERED (SomeNumber)
--=============================================================================
-- SetBased solution for loading a variable with 100,000 numbers.
-- Previous values in the variable are simply discarded to prevent display
-- or update IO from tainting the times.
--=============================================================================
--===== Clear cache and reset buffers
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
--===== Get starting resource values
SELECT @StartTime = GETDATE(),
@CPU = SUM(CPU),
@io = SUM(Physical_IO)
FROM Master.dbo.SysProcesses
WHERE SPID = @@SPID
SELECT @Bitbucket = SomeNumber
FROM SetBaseTest
ORDER BY SomeNumber
--===== Get ending resource values and calc totals
SELEC ''Set Based totas'' AS TestType,
DATEDIFF(ms,@StartTime,GETDATE()) AS TotalDurMS,
SUM(CPU) - @CPU AS TotalCPU,
SUM(Physical_IO) - @io AS TotalIO
FROM Master.dbo.SysProcesses
WHERE SPID = @@SPID
GO
--====== End of test... clenaup the test tables
IF OBJECT_I(''dbo.CursorTet'') IS NOT NULL
DROP TABLE CursorTest
IF OBJECT_I(''dbo.WhileTet'') IS NOT NULL
DROP TABLE WhileTest
IF OBJECT_I(''dbo.SetBaseTet'') IS NOT NULL
DROP TABLE SetBaseTest
The "key" though, is writting "good" set based code. First, "Set Based" doen''t necessarily mean "all in one SELECT". The other thing you have to watch for is triangular joins (half a cartesion join... tell tale sign is "<" or ">" in the join) and some forms of correlated subqueries.
For example, the following (running count example) looks like set based code, but t''s really RBAR (pronounced "ree-bar" and is a "Modenism" for "Row By Agonizing Row") on steriods...
SELECT p2.id, COUNT(p1.id)
FROM PERSONS p1,PERSONS p2
WHERE p1.id <= p2.id
GROUP BY p2.id
...takes days to process a lousy million rows... even a cursor is faster than that. Thee''s a dozen different ways to do this without RBAR and without a cursor or while loop if you look on this forum for them.<
--Jeff Moden
Change is inevitable... Change for the better is not.
January 21, 2007 at 10:07 pm
Thanx for the good work.
IIRC, you were supposed to write an article about this. How is that comming along?
January 22, 2007 at 5:51 pm
Its a bit difficult... I have a hard time thinking in a non-set based fashion when trying to write the cursor examples
--Jeff Moden
Change is inevitable... Change for the better is not.
January 22, 2007 at 10:13 pm
I'm sur eyou can find someone who can do that part for you .
January 22, 2007 at 10:23 pm
Heh... ya... hence the need for the article. Thanks for the reminder
--Jeff Moden
Change is inevitable... Change for the better is not.
Viewing 11 posts - 1 through 10 (of 10 total)
You must be logged in to reply to this topic. Login to reply