在应用程序中管理事务是一个复杂但至关重要的任务。事务确保了数据的一致性和完整性,特别是在涉及多个步骤或多个数据库操作时。本文将探讨在使用TransactionScope进行事务管理时可能遇到的一些常见问题和注意事项,以及如何克服这些问题。
当事务涉及多个不同的数据库时,就需要使用到Microsoft Distributed Transaction Coordinator(MSDTC)服务。MSDTC是一个Windows服务,为分布式系统提供事务基础设施。它确保事务具备ACID属性:原子性(Atomic)、一致性(Consistent)、隔离性(Isolated)和持久性(Durable)。
在TransactionScope中包含的事务,一旦涉及多个数据库,就会自动提升为“分布式事务”。这意味着任何服务器都必须启用MSDTC服务才能正常工作。如果服务未启用,调用将失败,并且可能不会得到很好的错误消息来帮助调试。
如果完全控制环境,这可能不是问题。但是,如果产品部署在本地,或者查询依赖于OPENQUERY或目标是可能不拥有的链接服务器,可能无法保证服务被启用。
在现代应用程序中,异步操作变得越来越普遍。async/await的出现使得异步操作变得更加容易,但如果正在处理事务,有一些事项需要注意。
异步/等待在TransactionScope块中默认情况下并不工作得很好,需要一些额外的配置来告诉它如何通过TransactionScopeAsyncFlowOption来解决操作。
using(var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
// 在这里执行异步/等待操作
}
另一个重要的考虑是,前一篇文章中讨论的ambient transaction是线程静态的,因此在多线程环境中不会按预期工作。总是使用最好的判断来决定是否将一系列事务组合在一起,以及是否有更好的选择适合环境。
管理超时在处理事务时至关重要,因为如果设置得太高或太低,都可能出现一系列问题。
非常长的超时可能导致阻塞/死锁问题,这很难调试,而那些设置得过短的超时可能无法完成任务。
如果遇到以下错误消息,那么可能需要重新评估现有的超时设置:
The transaction associated with the current connection has completed but has not been disposed. The transaction must be disposed before the connection can be used to execute SQL statements.
根据情况,可能想使用类似这样的设置,并称之为一天:
using(var transaction = new TransactionScope(TransactionScopeOption.Required, TimeSpan.MaxValue))
{
// 所有的事务操作都属于
}
绝对不要这样做。这几乎肯定会导致阻塞/死锁,这会让发疯,实际上也不会按预期工作。在大多数情况下,会收到之前的错误,这是因为任何TransactionScope的显式超时值总是受到应用程序配置中可能找到的maxTimeout选项的限制:
<system.transactions>
<!-- 此属性将覆盖代码中定义的任何值 -->
<machineSettings maxTimeout="01:00:00" />
</system.transactions>