Reputation: 313
I have a stored procedure in SQL Server 2008 R2 as follows;
DECLARE @uniqueId BIGINT
DECLARE TestCursor CURSOR FOR
SELECT em.uniqueId
FROM EMPLYEE_MASTER em WITH (NOLOCK)
INNER JOIN EMP_DETAILS edt WITH (NOLOCK) ON em.uniqueId = edt.uniqueId
INNER JOIN EMP_ATTENDANCE ea WITH (NOLOCK) ON em.uniqueId = ea.uniqueId
OPEN TestCursor
FETCH NEXT FROM TestCursor INTO @uniqueId
WHILE @@fetch_status = 0
BEGIN
BEGIN TRANSACTION purge
DELETE FROM EMPLYEE_MASTER where uniqueId = @uniqueId
DELETE FROM EMP_DETAILS where uniqueId = @uniqueId
DELETE FROM EMP_ATTENDANCE where uniqueId = @uniqueId
DELETE FROM EMP_ADDRESS where uniqueId = @uniqueId
DELETE FROM EMP_APPRAISALS where uniqueId = @uniqueId
COMMIT TRANSACTION purge
FETCH NEXT FROM TestCursor INTO @uniqueId
END
CLOSE TestCursor
DEALLOCATE TestCursor
This stored procedure hangs when it start running and blocks running other queries too (there are no referential integrity issues). I suspect the cause for the issue is due to (as you can see) SELECT
statement inside the cursor uses WITH (NOLOCK)
hint to dirty fetch. Then delete the data in the table that already uses in the select statement with WITH (NOLOCK)
statement inside the cursor. Because once I commented the delete statements that are uses with WITH (NOLOCK)
in the select statement in the cursor then stored procedure ran without any issue.
Can someone explain:
WITH (NOLOCK)
use in the statement inside the cursor and lock the records for DELETE (which runs within transaction)WITH (NOLOCK)
from select statement?Upvotes: 2
Views: 4491
Reputation: 1
Three things spring to mind: The cursor, the explicit transaction and the DELETE statements.
The cursor uses row level locking by default unless the number of rows exceeds n rows or x size. Where (n rows OR x size) > default, use table locks!
Either way, as the delete statement is trying to delete rows that are locked by the cursor! Irrelevant what locking you specify in the select part of the cursor.
Answers to your questions:
1) No.
2) Not quite. Replace the words "WITH (NOLOCK)" from your statement with "the same tables":
Cause for the issue is due to use of [the same tables] in the statement inside the cursor and lock the records for DELETE (which runs within transaction)
3) See answer by Ivan Starostin!
OR
--DECLARE @uniqueId BIGINT
DECLARE @uniqueIds TABLE (uniqueId BIGINT NOT NULL PRIMARY KEY)
--DECLARE TestCursor CURSOR FOR
INSERT INTO @uniqueIds
SELECT em.uniqueId
FROM EMPLYEE_MASTER em WITH (NOLOCK)
INNER JOIN EMP_DETAILS edt WITH (NOLOCK) ON em.uniqueId = edt.uniqueId
INNER JOIN EMP_ATTENDANCE ea WITH (NOLOCK) ON em.uniqueId = ea.uniqueId
--If the above insert fails, this is due to uniqueId NOT being unique!!
--OPEN TestCursor
--FETCH NEXT FROM TestCursor INTO @uniqueId
--WHILE @@fetch_status = 0
--BEGIN
BEGIN TRANSACTION purge
DELETE FROM EMPLYEE_MASTER where uniqueId --= @uniqueId
in (select uniqueId FROM @uniqueIds);
DELETE FROM EMP_DETAILS where uniqueId --= @uniqueId
in (select uniqueId FROM @uniqueIds);
DELETE FROM EMP_ATTENDANCE where uniqueId --= @uniqueId
in (select uniqueId FROM @uniqueIds);
DELETE FROM EMP_ADDRESS where uniqueId --= @uniqueId
in (select uniqueId FROM @uniqueIds);
DELETE FROM EMP_APPRAISALS where uniqueId --= @uniqueId
in (select uniqueId FROM @uniqueIds);
COMMIT TRANSACTION purge
--ROLLBACK?
-- FETCH NEXT FROM TestCursor INTO @uniqueId
END
--CLOSE TestCursor
--DEALLOCATE TestCursor
Upvotes: 0
Reputation: 9299
Remove absolutely unnecessary cursor, remove senseless NOLOCK.
DECLARE @del TABLE (uniqueId BIGINT NOT NULL PRIMARY KEY)
DELETE em
OUTPUT DELETED.uniqueId
INTO @del(uniqueId)
FROM EMPLYEE_MASTER em
WHERE EXISTS(SELECT 1 FROM EMP_DETAILS edt WHERE em.uniqueId = edt.uniqueId)
AND EXISTS(SELECT 1 FROM EMP_ATTENDANCE ea WHERE em.uniqueId = ea.uniqueId)
DELETE t
FROM EMP_DETAILS t
WHERE EXISTS(SELECT 1 FROM @del d WHERE d.uniqueId = t.uniqueId)
and so on
side note about cursor
DECLARE TestCursor CURSOR STATIC FOR
SELECT DISTINCT em.uniqueId
FROM EMPLYEE_MASTER em
INNER JOIN EMP_DETAILS edt ON em.uniqueId = edt.uniqueId
INNER JOIN EMP_ATTENDANCE ea ON em.uniqueId = ea.uniqueId
DISTINCT
- you are joining to DETAILS so the number of rows would be multiplied by details number which means you'd attempt to delete same uniqueId several times
STATIC
- instead of nolocking you could do this: with this option server will create temp table and store selected values in it.
Upvotes: 5