2

我有一个如下所示的存储过程:

USE [DBName]
GO
/****** Object:  StoredProcedure [dbo].[ProcName]    Script Date: 10/03/2012 12:10:16 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[ProcName](@filter bigint, @n int)
AS
DELETE FROM TableName WITH (READPAST, UPDLOCK, ROWLOCK) OUTPUT(DELETED.ColumnName2)
WHERE TableName.ID in (select top (@n) ID from TableName where TableName.ColumnName1 = @filter)

此过程返回 ColumnName2 的第一个 @n 值(这些值表示的记录将从表中删除)。我正在使用 vb 方法中的这个存储过程,它通常运行良好。但是,有时,由于我不知道的原因,它会引发异常:

You can only specify the READPAST lock in the READ COMMITTED or REPEATABLE READ isolation levels.

我的 vb 方法调用这个存储过程,如果它没有获得足够的值,那么它会生成新值并调用这个存储过程。重复此操作,直到有足够的值。本质上,TableName 就像一个队列,我的 vb 方法运行良好,但是,有时会抛出上面提到的异常。什么可能导致这种情况以及我的问题的解决方案是什么?

我试图为调用我的存储过程的代码启动一个新连接,但无济于事,因为再次引发了异常。尽管我已经阅读了以下文章,但我不知道可能是什么解决方案:

http://www.red-gate.com/messageboard/viewtopic.php?t=13614

调用存储过程时.NET READPAST 锁定错误

http://support.microsoft.com/kb/981995

http://blogs.technet.com/b/claudia_silva/archive/2011/08/08/replication-error-quot-you-can-only-specify-the-readpast-lock-in-the-read-committed- or-repeatable-read-isolation-levels-quot-generated-when-altering-published-table-columns.aspx

预先感谢您的任何帮助,

拉霍斯阿尔帕德。

4

2 回答 2

11

这应该可以解决您的问题

ALTER PROCEDURE [dbo].[ProcName](@filter bigint, @n int)
AS
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
DELETE FROM TableName WITH (READPAST, UPDLOCK, ROWLOCK) OUTPUT(DELETED.ColumnName2)
WHERE TableName.ID in (select top (@n) ID from TableName where TableName.ColumnName1 = @filter)

由于您使用的是需要此设置的 READPAST,因此您不妨在 SP 中明确设置它,因为它是安全范围的。

如果在存储过程或触发器中发出 SET TRANSACTION ISOLATION LEVEL,则当对象返回控制权时,隔离级别将重置为调用对象时有效的级别。

参考:SET TRANSACTION ISOLATION LEVEL

至于为什么会这样,一个可能的原因是连接池和 SQL 调用之间的混合事务隔离级别。

于 2012-10-03T09:30:27.557 回答
4

首先,DELETE 不正确。为了实现你想要的,你需要这样写:

WITH T AS (
   SELECT TOP (@n)
    ColumnName2
   FROM TableName WITH (READPAST, UPDLOCK, ROWLOCK) 
   WHERE ColumnName1 = @filter)
DELETE FROM T
OUTPUT DELETED.ColumnName2;

这种重写消除了 UPDATE 和顶部 ID 扫描之间的竞争条件。为了工作,WHERE 子句必须是 SARGable(即 ColumnName1 必须是索引),否则无论如何您最终都会进行扫描,而 READPAST 无济于事。有关更多详细信息,请参阅将表用作队列

现在回到你的问题:是什么导致不同的隔离级别?好吧,是您的代码中没有在此处发布的内容。如果我冒险尝试,它必须是TransactionScope使用默认构造函数构造的对象。请参阅Using new TransactionScope() Considered Harmful,了解这是如何发生的以及为什么不好。链接的文章还包含解决方案:使用事务范围的显式构造函数,它接受一个TransactionOptions您明确指定所需隔离级别的构造函数。

于 2012-10-03T10:32:43.793 回答