【问题标题】:TransactionScope causing blocking?TransactionScope 导致阻塞?
【发布时间】:2010-10-07 22:24:02
【问题描述】:

我正在针对数据库编写一些单元测试,并且我们正在使用事务来确保我们的测试数据在最后被删除。

我遇到了一个问题,我正在测试的方法正在使用它们自己的 TransactionScope 对象,并且在访问数据库时它似乎被阻塞了。

这是在我的测试的基类中:

BaseScope = new CommittableTransaction(new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUnCommitted, Timeout = new System.TimeSpan(0, 5, 0) });

然后在我正在测试的方法中,它确实:

using (TransactionScope scope = new TransactionScope())

第二个范围内的代码第一次接触数据库时,它会挂起。我有办法解决这个问题吗?

【问题讨论】:

    标签: c# transactions transactionscope


    【解决方案1】:

    如果您使用的是数据库,那么您就没有进行单元测试,您遇到的问题是真正的单元测试使用模拟和存根的原因之一。

    现在您正在做的测试非常有价值,在某些情况下,我实际上会做它们而不是单元测试。我将此标记为早期集成测试 (EIT)。这里的关键点是,当我们使用真实的东西而不是单元测试模拟时,我们发现了一类全新的错误。这里的关键是Real Thing。一旦您使用人为事务范围等伪造环境,您就会失去 EIT 的大部分好处,因为您没有发现细微的交互错误,或者(如您的情况)引入人为问题。

    我会找到一种方法,用足够的测试数据快速填充数据库,并将其恢复到测试之外的状态。 “重置为已知状态”脚本对此类测试非常有帮助。

    【讨论】:

    • 我认为这是我最终会选择的选项。我写了一些代码来创建一个新的/空的数据库,它在测试开始时运行,最后数据库被删除。
    【解决方案2】:

    当您嵌套TransactionScope 实例时,您最终会得到一个分布式事务,而不是一个简单的本地事务。这种行为在所使用的数据库之间有所不同。例如,除非实际涉及多个数据库,否则 SQLServer 2008 不会升级为 DTX。另一方面,Oracle 将始终升级为分布式事务,因为它不能不支持单个本地事务的共享连接。

    根据您使用的数据库和TransactionScopeOption,您最终可能会出现死锁。这是因为DTX 通常需要表锁来确保它们可以原子地提交。例如,在 Oracle 中,如果您在完成之前启动 DTX 并崩溃或失去连接,您可能会以“In Doubt Distributed Transaction”告终。此“不确定”事务可能会锁定一个或多个表,以防止其他会话修改它们,直到 DBA 对挂起的事务 ID 执行 ROLLBACK FORCE 命令。一些数据库(如 SQLServer)试图检测这种死锁并终止其中一个有问题的事务......但这肯定会发生。

    我会为您推荐两种选择之一:

    1. 确定您是否真的需要编写针对数据库的测试。 通常,您可以使用模拟或存根来避免编写更改然后回滚数据库的测试的需要。避免此类问题是有道理的,因为它既可以加快测试速度,又可以消除对它们的潜在依赖。但是,有时您不能这样做。
    2. 如果您确实需要针对数据库测试您的逻辑,请考虑修改您的代码,以便所有方法使用相同的数据库连接来执行其 SQL。这将消除分布式事务的创建,并有望解决您的问题。

    您可能还想查看数据库的待处理事务视图(在 Oracle 中称为 PENDING_TRANS$ ...在 SQLServer 中有 XACT_STATE() 函数)。

    【讨论】:

      【解决方案3】:

      您必须提交基本事务才能解除对测试方法的阻塞,我认为这不是您想要的排序行为。您需要让测试方法的事务加入在基类中创建的外部“环境”(伞/父/基/外部)事务,而不是尝试创建自己的。

      来自 MSDN CommittableTransactionClass 备注(强调我的):

      建议您创建 隐式事务使用 TransactionScope 类,使 环境事务上下文是 自动为您管理。 你 还应该使用 TransactionScope 和 DependentTransaction 类 需要使用的应用程序 同一笔交易跨多个 函数调用或多线程 电话。有关此的更多信息 模型,请参阅Implementing An Implicit Transaction Using Transaction Scope 主题。

      创建一个 CommittableTransaction 确实 不会自动设置环境 交易,即交易 您的代码在其中执行。您可以获取或 通过调用设置环境事务 的静态 Current 属性 全局事务对象。更多 有关环境交易的信息, 请参阅“管理事务流程 使用 TransactionScopeOption" 部分 实施一个隐式的 使用事务范围的事务 话题。如果环境事务是 未设置,对资源的任何操作 经理不是其中的一部分 交易。你需要明确 设置和重置环境事务, 以确保资源管理者 在正确的交易下运作 上下文。

      在提交 CommittableTransaction 之前,该事务涉及的所有资源仍处于锁定状态。

      正如 djna 指出的那样,使用事务来回滚测试期间所做的更改是相当滥用的。您的测试应该是一个好公民,并在 finally 子句中撤消和更改它对数据库的更改,以便在它之后运行的其他测试永远不会受到影响。如果您有很多表现不佳的测试,那么您现在可能不会走这条路。在这种情况下,将您的基础更改为使用范围设置为RequiresNew 的隐式事务,并在您的测试方法中使用Required

      【讨论】:

      • 我只想指出,我对此的反对票完全不相关,而且似乎是“报复”投票,因为我同时收到了对旧回复的一连串 5 票反对票:/跨度>
      • 谢谢 Damian :) 我很确定我的回答非常好。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-09
      • 1970-01-01
      • 1970-01-01
      • 2016-01-31
      • 1970-01-01
      • 1970-01-01
      • 2011-12-01
      相关资源
      最近更新 更多