2011-01-18 115 views
5

下表如何为读取操作 - 写入操作设置锁定?

Key(KeyId int, Sequence varchar(14))

考虑序列值是一个自定义自动增量键字母和数字组合特定的客户端需要他的系统。

我们做了一个名为GetNextSequence()的函数,它应该返回序列的下一个值。步骤来读取和更新程序进行如下

  1. 阅读使用keyid的序列:SELECT Sequence FROM [Key] WHERE KeyId = @Id
  2. 解析序列值,并确定下一个值
  3. 序列值写入表: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#中,我试图建立不同的锁组合就像使用表提示ROWLOCKHOLDLOCK事务隔离IsolationLevel.RepeatableReadIsolationLevel.Serializable或SQL,但没有成功。

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

+0

一边注意这听起来像这个问题:http://www.codinghorror.com/blog/2008/08/deadlocked.html – BrokenGlass

回答

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”可能不够,因为范围被锁定

+0

哦,我应该指定“解析序列”是在C#中,而不是在SQL Server中。 –

+1

@ Pierre-Alain Vigeant:你仍然需要提示和交易。你可以将c#移到CLR函数或SQL中吗? – gbn

+0

它似乎与'ROWLOCK,XLOCK,HOLDLOCK'一起工作,但我需要进一步的测试,明天我会做。 –

2

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

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

解决方案是独家锁;你可以使用XLOCK提示来指定它。独占锁基本上是为读取而获取的写入级别的锁,并且在这种情况下用于您希望写入您正在阅读的内容的情况。正如注释中所指出的那样,只有在明确的交易范围内执行该陈述时,才会保留排他性锁定,因此请确保在正在读取该值的“工作单元”期间设置一个锁定,确定如何前进它,然后更新它。

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

+0

除非在显式事务中,否则XLOCK仅保留当前语句。这本身还不够。所以如果用于SELECT,你仍然可以在UPDATE上死锁。 – gbn

+0

我假设一个明确的交易;函数GetNextSequence()从单词go开始是一个原子“工作单元”。 – KeithS

+0

的确如此,但它应该更加清楚排他锁的持久性 – gbn