我有一个经典的余额更新并发情况:
using (var context = GenerateContext())
{
var account = context.Accounts.First(x => x.id == accountId);
var balanceBefore = account.balance;
// ... do some other stuff ...
account.balance = balanceBefore + depositAmount;
context.SubmitChanges();
}
我想在此更新期间锁定整个表以进行读/写。
可以用Linq2SQL完成吗?
修改
所以我尝试了以下操作,它会在事务期间锁定表,但它不会执行更新 - 平衡永远不会更改。
using (var context = GenerateContext())
{
context.Connection.Open();
context.Transaction = context.Connection.BeginTransaction(IsolationLevel.Serializable);
var account = context.ExecuteQuery<Account>("SELECT TOP 1 * FROM [Account] WHERE [Id] = 10 WITH (TABLOCKX)").First();
var balanceBefore = account.balance;
// ... do some other stuff ...
account.balance = balanceBefore + depositAmount;
context.SubmitChanges();
}
我做错了什么?
答案 0 :(得分:1)
首先,最好在后端处理余额更新:
UPDATE Account
OUTPUT inserted.*
SET balance += @deposit
WHERE ID = @id;
但我们认为您只能在客户端上编写逻辑代码。您应该始终使用optimistic concurrency。在负载下乐观并发的好处太大而无法解雇。虽然悲观并发更容易编码(因为并发处理没有更新失败),但悲观并发在负载下表现不佳。
Linq2SQL支持乐观并发,请参阅Optimistic Concurrency: Overview。关键是Updatecheck
属性。但是,应用程序中的处理并发冲突比检测它们更棘手,但主题完全取决于实际的应用程序逻辑。
对于极端罕见的情况,当乐观并发不合适时,第一个尝试的解决方案是......仍然乐观并发,但增加了application locks。
如果您使用乐观并发并且您决定绝对必须采用悲观路线,那么即使世界将会结束,请使用(UPDLOCK, ROWLOCK)
并在ID
上拥有适当的索引。< / p>
答案 1 :(得分:0)
好的,我设法让它发挥作用。
我必须使用TransactionScope
封装所有内容,并将TransactionOptions
与隔离级别应用于事务范围。
现在看起来像这样:
using (var context = GenerateContext())
{
var txnOptions = new TransactionOptions();
txnOptions.IsolationLevel = System.Transactions.IsolationLevel.Serializable;
using (var txnScope = new TransactionScope(TransactionScopeOption.Required, txnOptions))
{
var account = context.ExecuteQuery<Account>("SELECT TOP 1 * FROM [Account] WITH (TABLOCKX) WHERE [Id] = 10").First();
var balanceBefore = account.balance;
// ... do some other stuff ...
account.balance = balanceBefore + depositAmount;
context.SubmitChanges();
txnScope.Complete();
}
}