如何为读操作 - 写操作设置锁?

时间:2011-01-18 19:35:30

标签: c# sql-server-2008

考虑下表

Key(KeyId int, Sequence varchar(14))

序列值是一个自定义自动增量键,它结合了特定客户端对其系统所需的字母和数字。

我们创建了一个名为GetNextSequence()的函数,该函数应该返回序列的下一个值。阅读和更新序列的步骤如下

  1. 使用KeyId:SELECT Sequence FROM [Key] WHERE KeyId = @Id
  2. 读取序列值
  3. 解析序列值并确定下一个值
  4. 将序列值写入表:UPDATE [Key] SET Sequence = @Sequence WHERE KeyId = @Id
  5. 这是C#代码(为简洁起见而简化):

    var transaction = connection.BeginTransaction(IsolationLevel.RepeatableRead);
    var currentSequenceValue = SqlUtils.ExecuteScalar(connection, transaction, "SELECT Sequence FROM [Key] WHERE KeyId = @Id", new SqlParameter("@Id", keyId));
    var updatedSequenceValue = ParseSequence(currentSequenceValue);
    SqlUtils.ExecuteScalar(connection, transaction, "UPDATE [Key] SET Sequence = @Sequence WHERE KeyId = @Id", new SqlParameter("@Id", keyId), new SqlParameter("@Sequence", updatedSequenceValue));
    transaction.Commit();
    return updatedSequenceValue;
    

    我们的问题在于两个不同的服务器可以访问相同的序列,最终导致死锁

    事务(进程ID X)在锁资源上与另一个进程发生死锁,并被选为死锁牺牲品。重新运行该交易。

    在C#中,我尝试使用表提示IsolationLevel.RepeatableReadIsolationLevel.Serializable设置不同的锁组合,如事务隔离ROWLOCKHOLDLOCK,或者在SQL中设置,但没有成功

    我希望每个服务器能够以原子方式读取,操作和更新序列。为这种情况设置锁的正确方法是什么?

2 个答案:

答案 0 :(得分:2)

问题是为获取读取而获取的默认锁定不会避免竞争条件,因为可以在同一记录上获取多个读锁定。

情况是,进程A获取第X行的读锁定。当A正在其“客户端”(在服务器程序内)工作时,进程B然后获取读取器锁。然后,当B在客户端工作时,A请求升级到写锁定,此时它被告知要等到B的读锁定被释放。 B然后请求写锁定并且等待直到A释放其读取。两者现在都在等待另一方,因此他们可以获得更独特的写锁定。

解决方案是独家锁定;您可以使用XLOCK提示指定此项。独占锁基本上是为读取获取的写级锁,并且在这种情况下使用,您希望在其中写入您正在阅读的内容。如注释中所述,只有在显式事务的范围内执行语句时才会保留排它锁,因此请确保在读取值的“工作单元”期间设置一个,确定如何提前它,然后更新它。

我会在行级(ROWLOCK)使用它,除非你一次更新很多类似的序列;如果您每个事务只处理一行,则获取页面或表级别的独占锁会使EVERYBODY等待您不需要的数据。

答案 1 :(得分:1)

我建议在事务期间使用独占的行级锁(ROWLOCK,XLOCK,HOLDLOCK)。到目前为止,你使用提示等是不够的。

BEGIN TRAN
    SELECT Sequence FROM [Key] WITH (ROWLOCK, XLOCK, HOLDLOCK) WHERE KeyId = @Id

    Parse the sequence value and determine the next value

    UPDATE [Key] SET Sequence = @Sequence WHERE KeyId = @Id
COMMIT

虽然,我看看至少将范围缩小到单一交易

    UPDATE [Key] WITH (ROWLOCK, XLOCK, HOLDLOCK)
    SET Sequence = dbo.scalarudf(...)
    WHERE KeyId = @Id

编辑:如果使用SERIALIZABLE,则不需要HOLDLOCK。并且“RepeatableRead”可能还不够,因为范围被锁定