考虑下表
序列值是一个自定义的自动递增键,结合了特定客户对其系统所需的字母和数字。
我们创建了一个名为 GetNextSequence() 的函数,它应该返回序列的下一个值。读取和更新序列的步骤如下
- 使用 KeyId 读取序列值:
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
或在 SQL 中使用表提示 ROWLOCK
和 HOLDLOCK
,但没有成功。
我希望每个服务器都能够以原子方式读取、操作和更新序列。在这种情况下设置锁的正确方法是什么?
最佳答案
问题是为读取获取的默认锁不能避免竞争条件,因为可以在同一条记录上获取多个读取锁。
情况是,进程 A 在 X 行上获取读锁。然后进程 B 在 A 在其“客户端”(在服务器程序中)工作时获取读锁。然后 A 请求升级写锁,而 B 在客户端工作,此时它被告知等待 B 的读锁被释放。然后 B 请求写锁并等待 A 释放它的读锁。两者现在都在等待对方,以便他们可以获得更独占的写入锁。
解决方案是独占锁;您可以使用 XLOCK 提示指定它。独占锁基本上是为读取获取的写入级锁,并且在这种情况下使用,您希望在这种情况下写入您正在阅读的内容。如评论中所述,仅当语句在显式事务范围内执行时才会维护独占锁,因此请确保在读取值的“工作单元”期间设置独占锁,确定如何推进它,然后更新它。
我会在行级别 (ROWLOCK) 使用它,除非您同时更新大量相似的序列;如果每个事务只处理一行,则获取页级或表级排他锁会使每个人都等待不需要的数据。
关于c# - 如何为读-操作-写操作设置锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4728217/