【发布时间】:2018-11-25 04:06:34
【问题描述】:
问题
这个问题分为三个部分:
- 为什么 Serializable 事务不能以原子方式执行操作?
- 假设答案是事务的原子性并不能保证其组成操作的原子性(并且它只确保所有操作要么全部成功或全部失败),那么为什么事务的隔离要求不能确保操作是原子的?我读过 Serializable 隔离级别确保事务的执行就像它们是串行执行的一样?
- 如果我对 Isolation 的解释不正确,那么正确的解释是什么?如何修改测试以证明使用序列化事务与根本不使用事务之间的区别。
一个最小的完整和可验证的例子
代码可以从here下载
假设 DataLayer (DAL) 由 WCF 服务实现,并且客户端代码包含从 Main 对其操作的调用:
public void Main(string[] args)
{
var dal = new DataLayerServiceClient();
var accounts = dal.GetAccounts();
int accountId = accounts.First().AccountId;
for (int i = 0; i < 10000; i++)
{
using (TransactionScope scope = new TransactionScope())
{
var account = dal.GetAccountById(accountId);
account.Balance++;
dal.Update(account);
scope.Complete();
}
}
}
假设:
- 客户端和服务已正确配置以将客户端事务传播到服务。 (通过观察存在环境事务,它具有分布式标识符并且该标识符与客户端上的标识符相同,在服务端对此进行了验证。
- 事务的隔离模式(在服务和客户端)是可序列化的(通过观察服务和客户端环境事务的属性来验证)
测试说明
同时运行两个客户端进程。
预期结果
预期结果是两个客户端退出后的账户余额应该比两个客户端启动前的余额大20000。
实际结果
两个客户端退出后的账户余额介于 10000 和 20000 之间。在某些情况下,其中一个客户端由于以下错误而中止:
事务(进程 ID)在锁定资源上与另一个死锁 进程并被选为死锁受害者
结论
每个客户端上包含在 TransactionScope 范围内的操作并未作为一个整体与其他客户端的操作串联运行。 来自两个事务的读取和写入是混合的,并且一些增量丢失了。
【问题讨论】:
-
@bommelding 我已经编辑了帖子以包含指向完整代码的链接。 @mjwills 你对更新的看法是正确的。这是更新的服务端代码:
command.CommandText = "UPDATE Accounts SET name = @Name, Balance = @Balance WHERE AccountId = @AccountId"; -
使用可序列化的 tx,您的 WHERE 中不需要并发列。
-
让我们分解一下:当您在客户端范围内仅执行 2 次调用时,您会在中断它时获得预期的行为吗?
-
@bommelding 你能解释一下为什么这个例子会导致死锁吗?
-
啊,你原来的问题不清楚。您声明了
The balance of the account after both clients exit is a value between 10000 and 20000. In some cases, one of the client is aborted due to the following error:,但不清楚这两件事是否相关(即
标签: c# wcf transactions acid