考虑下表
序列值是一个自定义自动增量键,它结合了特定客户端对其系统所需的字母和数字。
我们创建了一个名为GetNextSequence()的函数,该函数应该返回序列的下一个值。阅读和更新序列的步骤如下
SELECT Sequence FROM [Key] WHERE KeyId = @Id
UPDATE [Key] SET Sequence = @Sequence WHERE KeyId = @Id
这是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.RepeatableRead
和IsolationLevel.Serializable
设置不同的锁组合,如事务隔离ROWLOCK
或HOLDLOCK
,或者在SQL中设置,但没有成功
我希望每个服务器能够以原子方式读取,操作和更新序列。为这种情况设置锁的正确方法是什么?
答案 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”可能还不够,因为范围被锁定