{"id":1858,"date":"2014-06-18T14:07:44","date_gmt":"2014-06-18T14:07:44","guid":{"rendered":"http:\/\/www.sqlshack.com\/?p=1858"},"modified":"2019-03-18T15:15:40","modified_gmt":"2019-03-18T15:15:40","slug":"sql-server-cursor-performance-problems","status":"publish","type":"post","link":"https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/","title":{"rendered":"SQL Server cursor performance problems"},"content":{"rendered":"<br \/>\n<h2>Introduction<\/h2>\n<p>In a previous article we discussed the how to set up a basic cursor. We explained that a cursor is a row-based operation that takes a given SELECT statement and breaks downs the processing of the data into looping executions. People that do not have the background in Database Administration or who did not enjoy database classes while studying will often find SQL Server cursors handy and fun to write. This is because they break free of the shackles of set-based logic that is the normal when writing most T-SQL scripts. It is for this reason that we so often find SQL Server cursors written into the business logic of an application and it is a real pity because they are real performance hogs. Yes, there are times when cursors are OK to use and they can be tuned slightly by playing with their different types but, as a general rule of thumb, one should try to avoid them at all costs.<br \/>\n<\/br><br \/>\n    <!--more--><\/p>\n<h2>Performance problems<\/h2>\n<p>SQL Server cursors are notoriously bad for performance. In any good development environment people will talk about cursors as if they were demons to be avoided at all costs. The reason for this is plain and simple; they are the best way to slow down an application. This is because SQL Server, like any good relational database management system (RDBMS), is optimized for set-based operations. Take this simple SELECT statement as an example:<\/p>\n<pre>\r\nSELECT  *\r\nFROM    AdventureWorks2012.Sales.SalesOrderDetail\r\nWHERE   ModifiedDate BETWEEN '2008-07-15 00:00:00.000' AND '2008-07-31 00:00:00.000'\r\n    <\/pre>\n<p>When you write a SELECT statement like this (that returns 1051 rows) to fetch a bunch of data from that database the system receives the statement and creates or uses an existing query plan, then it uses indexes to locate the data on the disk, fetches the data in one foul swoop and returns the data as a set. If your indexes are correctly placed the query can be sped up. In the case above if the ModifiedDate field was included in an index it would run faster.<\/p>\n<p>When running this query and turning time statistics on (<span style=\"font-family:'Courier New'; color:#0000FF;\">SET STATISTICS TIME ON<\/span>) one can see that the entire process takes less than a second:<\/p>\n<p style=\"font-family:'Courier New';\">SQL Server Execution Times:<br \/>\n    &nbsp;&nbsp;&nbsp;&nbsp;CPU time = 15 ms,  elapsed time = 87 ms.\n    <\/p>\n<p>Now let\u2019s say you wanted (for some reason) to replace your WHERE statement with a variable that can be used to call a single row each time but 1051 times you can use a cursor to do so:<\/p>\n<pre>\r\nDECLARE @rowguidVar UNIQUEIDENTIFIER  -- prepare unique ID variable to use in the WHERE statement below\r\n\r\nDECLARE test_cursor CURSOR FOR  \r\nSELECT rowguid\r\nFROM   AdventureWorks2012.Sales.SalesOrderDetail\r\nWHERE  ModifiedDate BETWEEN '2008-07-15 00:00:00.000' AND '2008-07-31 00:00:00.000'\r\n--This is the same query as above except we SELECT only the ID for each row\r\n\r\nOPEN test_cursor   \r\nFETCH NEXT FROM test_cursor INTO @rowguidVar   \r\n--This is the start of the cursor loop. \r\nWHILE @@FETCH_STATUS = 0   \r\nBEGIN   \r\n       SELECT *\r\n          FROM            Sales.SalesOrderDetail\r\n          WHERE    rowguid = @rowguidVar\r\n-- Here we select on row and then move onto the next row ID and loop\r\n       FETCH NEXT FROM test_cursor INTO @rowguidVar   \r\nEND\r\n\r\nCLOSE test_cursor   \r\nDEALLOCATE test_cursor\r\n-- Don't forget these statements which flush the cursor from memory\r\n    <\/pre>\n<p>Results: <img decoding=\"async\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" data-src=\"\/wp-content\/uploads\/2014\/06\/AdventureWorks.png\" alt=\"Results gained by using a SQL Server cursor\" \/><noscript><img decoding=\"async\" src=\"\/wp-content\/uploads\/2014\/06\/AdventureWorks.png\" alt=\"Results gained by using a SQL Server cursor\" \/><\/noscript><\/p>\n<p>This cursor will fetch exactly the same set of data but it does it on a row by row basis, and it takes  heck of a lot longer to do so, as a matter of fact 48 seconds as opposed to 87 milliseconds, that\u2019s 55172% slower! This is because the set-based logic for which RDBMS systems like SQL Server are optimized is completely broken and the entire query process has to be repeated for each row.<\/p>\n<h2>So why do they exist?<\/h2>\n<p>SQL Server cursors and any other type of cursors date back to before procedural programming languages could handle sets of data and required to be split into rows (E.g. COBOL, FORTRAN, old style C etc.) So in that regard they are just plain old-fashioned. However, other than for backwards compatibility they can still serve us well in the right situations. One such time would be when you want to write a script to restore a bunch of databases from backup files on a disk. In this case you can write a cursor to collect the database names and run a RESTORE DATABASE command for each database in a one-by-one fashion. Another time this may be useful is when you need to update an entire column of a large table that is constantly being queried in a production environment. Doing this on a row-by-row basis would avoid locks and waits for other users and your UPDATE query while concurrent operations are happening on the same pages of data. However, even in this case it is usually preferable to write a WHILE loop to update sets of data (i.e. on a 1000 by 1000 row basis). This would also avoid too many locks and would do the job quicker. <\/p>\n<p>To illustrate how a while loop works I have massaged the above query example to return the same data again but this time, instead of row-by-row or a full set, it does something in between and returns 100 rows at a time.<\/p>\n<pre>\r\nDECLARE @GUIDS TABLE (rowguid UNIQUEIDENTIFIER PRIMARY KEY)\r\n--Here we create an indexed table variable to store all the GUIDs\r\n\r\nINSERT INTO    @GUIDS\r\nSELECT         rowguid\r\nFROM           AdventureWorks2012.Sales.SalesOrderDetail\r\nWHERE          ModifiedDate BETWEEN '2008-07-15 00:00:00.000' AND '2008-07-31 00:00:00.000'\r\n--Here we insert all our GUIDs into the variable\/temporary table\r\n\r\nWHILE EXISTS\t(SELECT rowguid\r\n\t\t\t\t            FROM\t@GUIDS)\r\n--This is a basic WHILE loop that runs as long as there is data in the variable table\r\n\r\nBEGIN\r\nSELECT *\r\nFROM   AdventureWorks2012.Sales.SalesOrderDetail\r\nWHERE  rowguid IN\t(SELECT\tTOP 100 * \r\n                                FROM\t@GUIDS)\r\n--We SELECT the top 100 rows that are in our variable table\r\n\r\nDELETE TOP (100)\r\nFROM @GUIDS\r\n--This deletes the 100 rows that we have just selected\\\r\n\r\nEND\r\n--If there is still data in the variable table we return to the BEGIN point and process the next 100\r\n    <\/pre>\n<p>Even this clumsy WHILE loop is blisteringly fast in comparison to the SQL Server cursor approach. It takes less than a second but is closer to 800ms than 87ms as is the case for the pure set-based query.<\/p>\n<h2>Conclusion<\/h2>\n<p>People are right to loath cursors. If it becomes normal for you to use cursors in your T-SQL whilst building the business logic of an application you are heading off down a path to disastrous performance. Imagine, for example, you wrote a stored procedure that returns results based on a cursor and then you write another stored procedure using a cursor that calls the first one. This is called nested cursors and it is a perfect way to bog down expensive\/performant server equipment with sloppy, badly performing code.<\/p>\n<p>So, avoid cursors more than you would avoid your mother-in-law and only use them when you have mastered set-based T-SQL and you know that a row-by-row approach is needed and only for a one off maintenance\/patch-script operation. <\/p>\n<h2>\n        Useful resources<\/h2>\n<ul>\n<li>\n        <a href=\"\/sql-server-cursor-tutorial\/\">SQL Server cursor tutorial<\/a> <\/li>\n<li><a href=\"http:\/\/technet.microsoft.com\/en-us\/library\/aa172595(v=sql.80).aspx\" target=\"_blank\">Transact-SQL Cursors<\/a><\/li>\n<\/ul>\n<div id=\"see_more\"><\/div>\n<p><script>\ndisplay_see_more(text='plan', video='plan', banner='plan', banner_link='plan');\n<\/script><br \/>\n<\/br><\/p>\n<!-- AddThis Advanced Settings generic via filter on the_content --><!-- AddThis Share Buttons generic via filter on the_content -->","protected":false},"excerpt":{"rendered":"<p>Introduction In a previous article we discussed the how to set up a basic cursor. We explained that a cursor is a row-based operation that takes a given SELECT statement and breaks downs the processing of the data into looping executions. People that do not have the background in Database Administration or who did not [&hellip;]<!-- AddThis Advanced Settings generic via filter on wp_trim_excerpt --><!-- AddThis Share Buttons generic via filter on wp_trim_excerpt --><\/p>\n","protected":false},"author":15,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24],"tags":[],"class_list":["post-1858","post","type-post","status-publish","format-standard","hentry","category-sql-server-cursors"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>SQL Server cursor performance problems<\/title>\n<meta name=\"description\" content=\"This article provides an explanation about performance problems caused by using SQL Server cursors, describes why these cursors exist\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"SQL Server cursor performance problems\" \/>\n<meta property=\"og:description\" content=\"This article provides an explanation about performance problems caused by using SQL Server cursors, describes why these cursors exist\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/\" \/>\n<meta property=\"og:site_name\" content=\"SQL Shack - articles about database auditing, server performance, data recovery, and more\" \/>\n<meta property=\"article:published_time\" content=\"2014-06-18T14:07:44+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2019-03-18T15:15:40+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.sqlshack.com\/wp-content\/uploads\/2014\/06\/AdventureWorks.png\" \/>\n<meta name=\"author\" content=\"Evan Barke\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Evan Barke\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/sql-server-cursor-performance-problems\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/sql-server-cursor-performance-problems\\\/\"},\"author\":{\"name\":\"Evan Barke\",\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/#\\\/schema\\\/person\\\/ab4f2034466266f96b18d70277ef5f3b\"},\"headline\":\"SQL Server cursor performance problems\",\"datePublished\":\"2014-06-18T14:07:44+00:00\",\"dateModified\":\"2019-03-18T15:15:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/sql-server-cursor-performance-problems\\\/\"},\"wordCount\":909,\"commentCount\":1,\"publisher\":{\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/#organization\"},\"articleSection\":[\"Cursors\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/sql-server-cursor-performance-problems\\\/\",\"url\":\"https:\\\/\\\/www.sqlshack.com\\\/sql-server-cursor-performance-problems\\\/\",\"name\":\"SQL Server cursor performance problems\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/#website\"},\"datePublished\":\"2014-06-18T14:07:44+00:00\",\"dateModified\":\"2019-03-18T15:15:40+00:00\",\"description\":\"This article provides an explanation about performance problems caused by using SQL Server cursors, describes why these cursors exist\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.sqlshack.com\\\/sql-server-cursor-performance-problems\\\/\"]}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/#website\",\"url\":\"https:\\\/\\\/www.sqlshack.com\\\/\",\"name\":\"SQL Shack - articles about database auditing, server performance, data recovery, and more\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.sqlshack.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/#organization\",\"name\":\"SQL Shack\",\"url\":\"https:\\\/\\\/www.sqlshack.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/www.sqlshack.com\\\/wp-content\\\/uploads\\\/2019\\\/03\\\/sqlshack-default.png\",\"contentUrl\":\"https:\\\/\\\/www.sqlshack.com\\\/wp-content\\\/uploads\\\/2019\\\/03\\\/sqlshack-default.png\",\"width\":1200,\"height\":630,\"caption\":\"SQL Shack\"},\"image\":{\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.sqlshack.com\\\/#\\\/schema\\\/person\\\/ab4f2034466266f96b18d70277ef5f3b\",\"name\":\"Evan Barke\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/ce31fd9e332521654d48f46d4a93ca1aeb234bb56695a75489830d2e9aea1770?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/ce31fd9e332521654d48f46d4a93ca1aeb234bb56695a75489830d2e9aea1770?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/ce31fd9e332521654d48f46d4a93ca1aeb234bb56695a75489830d2e9aea1770?s=96&d=mm&r=g\",\"caption\":\"Evan Barke\"},\"description\":\"Having worked on highly transactional production systems and advanced corporate business intelligence, Evan is now using this experience make a difference in the eHealth world. He is driven by the love of technology and a desire to solve complex problems creatively. View all posts by Evan Barke\",\"url\":\"https:\\\/\\\/www.sqlshack.com\\\/author\\\/evan-barke\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"SQL Server cursor performance problems","description":"This article provides an explanation about performance problems caused by using SQL Server cursors, describes why these cursors exist","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/","og_locale":"en_US","og_type":"article","og_title":"SQL Server cursor performance problems","og_description":"This article provides an explanation about performance problems caused by using SQL Server cursors, describes why these cursors exist","og_url":"https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/","og_site_name":"SQL Shack - articles about database auditing, server performance, data recovery, and more","article_published_time":"2014-06-18T14:07:44+00:00","article_modified_time":"2019-03-18T15:15:40+00:00","og_image":[{"url":"https:\/\/www.sqlshack.com\/wp-content\/uploads\/2014\/06\/AdventureWorks.png","type":"","width":"","height":""}],"author":"Evan Barke","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Evan Barke","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/#article","isPartOf":{"@id":"https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/"},"author":{"name":"Evan Barke","@id":"https:\/\/www.sqlshack.com\/#\/schema\/person\/ab4f2034466266f96b18d70277ef5f3b"},"headline":"SQL Server cursor performance problems","datePublished":"2014-06-18T14:07:44+00:00","dateModified":"2019-03-18T15:15:40+00:00","mainEntityOfPage":{"@id":"https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/"},"wordCount":909,"commentCount":1,"publisher":{"@id":"https:\/\/www.sqlshack.com\/#organization"},"articleSection":["Cursors"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/","url":"https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/","name":"SQL Server cursor performance problems","isPartOf":{"@id":"https:\/\/www.sqlshack.com\/#website"},"datePublished":"2014-06-18T14:07:44+00:00","dateModified":"2019-03-18T15:15:40+00:00","description":"This article provides an explanation about performance problems caused by using SQL Server cursors, describes why these cursors exist","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.sqlshack.com\/sql-server-cursor-performance-problems\/"]}]},{"@type":"WebSite","@id":"https:\/\/www.sqlshack.com\/#website","url":"https:\/\/www.sqlshack.com\/","name":"SQL Shack - articles about database auditing, server performance, data recovery, and more","description":"","publisher":{"@id":"https:\/\/www.sqlshack.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.sqlshack.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.sqlshack.com\/#organization","name":"SQL Shack","url":"https:\/\/www.sqlshack.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.sqlshack.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.sqlshack.com\/wp-content\/uploads\/2019\/03\/sqlshack-default.png","contentUrl":"https:\/\/www.sqlshack.com\/wp-content\/uploads\/2019\/03\/sqlshack-default.png","width":1200,"height":630,"caption":"SQL Shack"},"image":{"@id":"https:\/\/www.sqlshack.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.sqlshack.com\/#\/schema\/person\/ab4f2034466266f96b18d70277ef5f3b","name":"Evan Barke","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/ce31fd9e332521654d48f46d4a93ca1aeb234bb56695a75489830d2e9aea1770?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/ce31fd9e332521654d48f46d4a93ca1aeb234bb56695a75489830d2e9aea1770?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/ce31fd9e332521654d48f46d4a93ca1aeb234bb56695a75489830d2e9aea1770?s=96&d=mm&r=g","caption":"Evan Barke"},"description":"Having worked on highly transactional production systems and advanced corporate business intelligence, Evan is now using this experience make a difference in the eHealth world. He is driven by the love of technology and a desire to solve complex problems creatively. View all posts by Evan Barke","url":"https:\/\/www.sqlshack.com\/author\/evan-barke\/"}]}},"_links":{"self":[{"href":"https:\/\/www.sqlshack.com\/wp-json\/wp\/v2\/posts\/1858","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlshack.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlshack.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlshack.com\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlshack.com\/wp-json\/wp\/v2\/comments?post=1858"}],"version-history":[{"count":13,"href":"https:\/\/www.sqlshack.com\/wp-json\/wp\/v2\/posts\/1858\/revisions"}],"predecessor-version":[{"id":45616,"href":"https:\/\/www.sqlshack.com\/wp-json\/wp\/v2\/posts\/1858\/revisions\/45616"}],"wp:attachment":[{"href":"https:\/\/www.sqlshack.com\/wp-json\/wp\/v2\/media?parent=1858"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlshack.com\/wp-json\/wp\/v2\/categories?post=1858"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlshack.com\/wp-json\/wp\/v2\/tags?post=1858"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}