首页 > 解决方案 > 从 WCF 客户端传播到 WCF 服务的事务的隔离是什么意思?

问题描述

问题

这个问题分为三个部分:

  1. 为什么 Serializable 事务不能原子地执行操作?
  2. 假设答案是事务的原子性并不能保证其组成操作的原子性(并且它只确保所有操作都成功或全部失败),为什么事务的隔离要求不能确保操作是原子的? 我读过 Serializable 隔离级别确保事务的执行就像它们是串行执行的一样?
  3. 如果我对 Isolation 的解释不正确,那么正确的解释是什么?我如何修改测试以证明使用序列化事务与根本不使用事务之间的区别。

一个最小的完整和可验证的例子

代码可以从这里下载

假设 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();
            }
        }
    }

还假设:

  1. 正确配置客户端和服务以将客户端事务传播到服务。(通过观察存在环境事务,它具有分布式标识符并且该标识符与客户端上的标识符相同,这在服务端得到了验证。
  2. 事务的隔离模式(在服务和客户端)是可序列化的(通过观察环境事务在服务和客户端的属性来验证)

测试说明

同时运行两个客户端进程。

预期结果

预期的结果是两个客户端退出后的账户余额应该比两个客户端启动前的余额大 20000。

实际结果

两个客户端退出后的账户余额介于 10000 和 20000 之间。在某些情况下,其中一个客户端由于以下错误而中止:

事务(进程 ID)在锁资源上与另一个进程死锁,并已被选为死锁牺牲品

结论

每个客户端上包含在 TransactionScope 范围内的操作并不作为一个整体与其他客户端的操作串联运行。来自两个事务的读取和写入是混合的,并且一些增量丢失了。

标签: c#wcftransactionsacid

解决方案


  1. 问题:“为什么一个可序列化事务不能以原子方式执行操作”
    :一个可序列化事务是原子的,因为它的所有操作要么全部成功,要么全部失败。这个例子没有反驳。
  2. 问题:“为什么事务的 Isolation 要求不能保证操作是原子的?
    :两个 Serializable 事务满足 Isolation 要求,因为它们不会同时运行。如果两个尝试一起运行,一个将按计划继续,并且其他被中止。这正是问题中报告的异常发生的原因。(“事务(进程 ID)在锁定资源上与另一个进程死锁,并已被选为死锁受害者”)。
  3. 问题:“如果我对隔离的解释不正确,那么正确的解释是什么?”。
    回答:问题中的错误假设是,如果两个事务不能同时运行,一个将等待另一个完成然后继续。这是不正确的。事实上,可序列化事务是隔离的,不会同时运行,但这并不意味着一个会等待另一个。事务的隔离与持有互斥锁以单独执行一组操作不同。
    问题:“我如何修改测试以证明使用序列化事务与根本不使用事务之间的区别”
    答案:以下代码段将演示如何使用事务来确保增量按预期发生。

        int i = 0;
        while(i < 10000)
        {
            try
            {
                using (TransactionScope scope = new TransactionScope())
                {
                    var account = dal.GetAccountById(accountId);
                    account.Balance++;
                    dal.Update(account);
    
                    scope.Complete();
                }
                i++;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"{ex.Message} : restarting");
            }
        }
    

当然,这是非常低效的,但它按预期工作,并演示了事务如何隔离资源管理器上的操作。


推荐阅读