Usually minimal complete verifiable examples should be part of a good question. Here I am trying to answer with two of them:
1. Minimum cursor script that yields -9 ("not fetch")
Please note that @@FETCH_STATUS
will never accept this value (it will return 0). Only internal control structures have this as initial value for declared and never received cursors (whether open or not).
DECLARE [cursor-9] CURSOR FOR SELECT null FROM sys.tables SELECT [fetch_status] FROM sys.dm_exec_cursors(@@SPID) WHERE name='cursor-9' DEALLOCATE [cursor-9]
Further link: fooobar.com/questions/1013479 / ...
2. Minimum cursor script that will return -2 ("row missing")
Concurrency requires two connections. I used two instances of SSMS connected to 11.0.6020 with one script window, each of which is connected to the same empty database.
Run this script in the first instance:
DECLARE @@id int CREATE TABLE tmp41307323 ( Id int not null PRIMARY KEY ) INSERT INTO tmp41307323 (Id) VALUES (1),(2) DECLARE [cursor-2] CURSOR KEYSET FOR SELECT Id FROM tmp41307323 ORDER BY Id OPEN [cursor-2] FETCH NEXT FROM [cursor-2] INTO @@id
He will create a table with a primary key, add two rows and open the cursor on it. Without the KEYSET
you get -1 instead of -2 because that's how the keys work: SQL Server creates a temporary table in tempdb that contains only ordered unique key values ββfor each row that the cursor selects. Then, when we get it, it scans the keys for the next row to retrieve into the temporary table and selects only that row from the real table. This, and only this, scenario is therefore susceptible to simultaneous deletions. In addition, he will see the changes made to any non-key columns of the source table made during the extraction.
Run this script in the second instance:
DELETE FROM tmp41307323 WHERE Id=2
Thus, we delete the line that the cursor will expect in the first instance when it selects the next.
Finally, run this script in the first instance (do not disable so that the cursor is still in scope):
DECLARE @@id int FETCH NEXT FROM [cursor-2] INTO @@id SELECT @@FETCH_STATUS [@@FETCH_STATUS] CLOSE [cursor-2] DEALLOCATE [cursor-2] DROP TABLE tmp41307323
Result:
It will work the same when launched in the same batch in the same connection with DELETE
before the second FETCH
. An installation with two connections demonstrates it in a realistic context, assuming that a developer who knows the KEYSET
keyword will not specifically delete during extraction and will have no side effects in the cursor loop causing such deletions (using both cursors and triggers about the same abomination, like html manipulation with regular expression).