当WITH(NOLOCK)在光标内部使用时,存储过程挂起

时间:2017-03-15 07:26:35

标签: sql-server sql-server-2008 stored-procedures

我在SQL Server 2008 R2中有一个存储过程,如下所示;

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

此存储过程在开始运行时会挂起并阻止运行其他查询(没有参照完整性问题)。我怀疑问题的原因是由于(正如你所看到的)SELECT语句中的语句使用WITH (NOLOCK)提示脏提取。然后删除已在select语句中使用的表中的数据,并在游标中使用WITH (NOLOCK)语句。因为一旦我在游标中的select语句中注释了与WITH (NOLOCK)一起使用的删除语句,那么存储过程运行没有任何问题。

有人可以解释一下:

  1. 我怀疑是对的吗?
  2. 问题的原因是由于WITH (NOLOCK)在游标内的语句中使用并锁定DELETE的记录(在事务中运行)
  3. 如何防止此问题?从select语句中删除WITH (NOLOCK)

2 个答案:

答案 0 :(得分:3)

删除绝对不必要的光标,删除无意义的NOLOCK。

action="<?php echo esc_url( home_url( '/' ) ); ?>"

等等

关于光标的附注

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)

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 - 您正在加入DETAILS,因此行数将乘以详细信息数量,这意味着您将尝试多次删除相同的uniqueId

DISTINCT - 而不是 nolocking 你可以这样做:使用此选项服务器将创建临时表并在其中存储选定的值。

答案 1 :(得分:0)

想到三件事: 游标,显式事务和DELETE语句。

默认情况下,除非行数超过n行或x大小,否则游标将使用行级别锁定。其中(n行或x大小)>默认值,请使用表锁!

无论哪种方式,因为delete语句都试图删除被光标锁定的行!与您在光标的选择部分中指定的锁定无关。

回答您的问题:

1)不。

2)不完全是。将语句中的“ WITH(NOLOCK)”替换为“相同表”:

此问题的原因是由于在游标内的语句中使用了[相同表] 并锁定DELETE(在事务内运行)的记录

3)参见伊万·斯塔诺斯汀的回答!

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