This post is just a reminder to be attentive to the locking overhead of your Transact-SQL server cursors.
For example, the following cursor is using default options in SQL Server 2012 to iterate row-by-row through the Employee table. I’m declaring a variable and populating it with the BusinessEntityID from each row (and I’m not doing anything with it – as I just wanted to avoid the chatter back to my SQL Server Management Studio window):
1: DECLARE Employee_Cursor CURSOR FOR
3: SELECT BusinessEntityID
4: FROM [HumanResources].[Employee];
6: SELECT properties
7: FROM sys.dm_exec_cursors(52);
9: OPEN Employee_Cursor;
11: DECLARE @BusinessEntityID int;
13: FETCH NEXT FROM Employee_Cursor
14: INTO @BusinessEntityID;
16: WHILE @@FETCH_STATUS = 0
19: FETCH NEXT FROM Employee_Cursor
20: INTO @BusinessEntityID;
24: CLOSE Employee_Cursor;
Now what kind of locking behavior did I see? Using extended events and tracking lock_acquired and lock_released, I saw 2,340 events total. Below is just a bit of what I saw – with the IS mode acquired for object, then IS mode lock for page, then S lock mode for the key, released for that key, acquired for the next key, released, and then released for the page and object and then starting all over again until reaching the final row of the table.
I also included a call to sys.dm_exec_cursors for my test session id (52). This returned the following property value:
TSQL | Dynamic | Optimistic | Global (0)
What about using SCROLL_LOCKS? (TSQL | Dynamic | Scroll Locks | Global (0)). Here is the slightly modified T-SQL code and abridged result set:
1: DECLARE Employee_Cursor CURSOR SCROLL_LOCKS
4: SELECT BusinessEntityID
5: FROM [HumanResources].[Employee];
7: SELECT properties
8: FROM sys.dm_exec_cursors(55);
10: OPEN Employee_Cursor;
12: FETCH NEXT FROM Employee_Cursor;
14: WHILE @@FETCH_STATUS = 0
17: FETCH NEXT FROM Employee_Cursor;
21: CLOSE Employee_Cursor;
22: DEALLOCATE Employee_Cursor;
Now we see IX (object), IU (page) and U (key) modes enter the mix. By the way, the 1237579447 is associated with the Employee table. The 72057594045136896 value is the container_id – which is a type “1” (IN_ROW_DATA), so we can cross reference to sys.partitions.hobt_id, which then correlated to the Employee table.
Are the locks necessary? Maybe, depending on what you wish to achieve, but I find that often the cursor settings are not configured intentionally.
As an exercise, you can check sys.dm_exec_cursor to see cursors running on your server at that moment in time. This DMF will surface the session IDs, cursor name, properties and the associated sql_handle. You may see settings that are unexpected or are overkill for what is actually required.
0 thoughts on “Being Mindful of Cursor Lock Overhead”
Does FAST_FORWARD do the same level of locking? I can count on one hand the number of times I have seen cursors used by clients where FAST_FORWARD wasn’t used/appropriate. I would hope it takes much less locking.
In my tests I saw IS object and IS page mode locks for FAST_FORWARD. So this is more ideal than the default, if we’re trying to keep the locks to a minimum (versus S mode or U mode).
I’ve seen a mix over the years. Just today I saw a significant amount of default cursor usage (not FAST_FORWARD).
Comments are closed.