Kra*_*ram 10 c# performance transactionscope distributed-transactions
使用TransactionScope对象来设置不需要跨函数调用传递的隐式事务非常棒!但是,如果连接打开而另一个连接已打开,则事务协调器会静默升级要分发的事务(需要MSDTC服务运行并占用更多资源和时间).
所以,这很好:
using (var ts = new TransactionScope())
{
using (var c = DatabaseManager.GetOpenConnection())
{
// Do Work
}
using (var c = DatabaseManager.GetOpenConnection())
{
// Do more work in same transaction using different connection
}
ts.Complete();
}
Run Code Online (Sandbox Code Playgroud)
但这会使交易升级:
using (var ts = new TransactionScope())
{
using (var c = DatabaseManager.GetOpenConnection())
{
// Do Work
using (var nestedConnection = DatabaseManager.GetOpenConnection())
{
// Do more work in same transaction using different nested connection - escalated transaction to distributed
}
}
ts.Complete();
}
Run Code Online (Sandbox Code Playgroud)
是否有建议的做法是避免以这种方式升级事务,同时仍然使用嵌套连接?
我现在能想到的最好的是拥有一个ThreadStatic连接并重新使用如果设置了Transaction.Current,就像这样:
public static class DatabaseManager
{
private const string _connectionString = "data source=.\\sql2008; initial catalog=test; integrated security=true";
[ThreadStatic]
private static SqlConnection _transactionConnection;
[ThreadStatic] private static int _connectionNesting;
private static SqlConnection GetTransactionConnection()
{
if (_transactionConnection == null)
{
Transaction.Current.TransactionCompleted += ((s, e) =>
{
_connectionNesting = 0;
if (_transactionConnection != null)
{
_transactionConnection.Dispose();
_transactionConnection = null;
}
});
_transactionConnection = new SqlConnection(_connectionString);
_transactionConnection.Disposed += ((s, e) =>
{
if (Transaction.Current != null)
{
_connectionNesting--;
if (_connectionNesting > 0)
{
// Since connection is nested and same as parent, need to keep it open as parent is not expecting it to be closed!
_transactionConnection.ConnectionString = _connectionString;
_transactionConnection.Open();
}
else
{
// Can forget transaction connection and spin up a new one next time one's asked for inside this transaction
_transactionConnection = null;
}
}
});
}
return _transactionConnection;
}
public static SqlConnection GetOpenConnection()
{
SqlConnection connection;
if (Transaction.Current != null)
{
connection = GetTransactionConnection();
_connectionNesting++;
}
else
{
connection = new SqlConnection(_connectionString);
}
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
return connection;
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:那么,如果答案是在它嵌套在事务管理器内时重用相同的连接,就像上面的代码那样,我想知道在事务处理中处理这个连接的含义.
到目前为止,我可以看到(使用Reflector检查代码),重置连接的设置(连接字符串等)并关闭连接.所以(理论上),重新设置连接字符串并在后续调用中打开连接应该"重用"连接并防止升级(我的初始测试同意这一点).
虽然看起来确实有点hacky ......而且我确信某个地方必须有一个最佳实践,表明在被处理之后不应该继续使用它!
但是,由于我无法继承密封的SqlConnection,并希望维护与事务无关的连接池友好方法,所以我很难(但会很高兴)看到更好的方法.
此外,意识到如果应用程序代码尝试打开嵌套连接(在大多数情况下,在我们的代码库中是不必要的),我可以通过抛出异常来强制非嵌套连接
public static class DatabaseManager
{
private const string _connectionString = "data source=.\\sql2008; initial catalog=test; integrated security=true; enlist=true;Application Name='jimmy'";
[ThreadStatic]
private static bool _transactionHooked;
[ThreadStatic]
private static bool _openConnection;
public static SqlConnection GetOpenConnection()
{
var connection = new SqlConnection(_connectionString);
if (Transaction.Current != null)
{
if (_openConnection)
{
throw new ApplicationException("Nested connections in transaction not allowed");
}
_openConnection = true;
connection.Disposed += ((s, e) => _openConnection = false);
if (!_transactionHooked)
{
Transaction.Current.TransactionCompleted += ((s, e) =>
{
_openConnection = false;
_transactionHooked = false;
});
_transactionHooked = true;
}
}
connection.Open();
return connection;
}
}
Run Code Online (Sandbox Code Playgroud)
仍然会重视一个不太讨厌的解决方案:)
事务升级的主要原因之一是事务中涉及多个(不同的)连接。这几乎总是升级为分布式事务。这确实是一种痛苦。
这就是为什么我们确保所有事务都使用单个连接对象。做这件事有很多种方法。大多数情况下,我们使用线程静态对象来存储连接对象,而执行数据库持久化工作的类则使用线程静态连接对象(当然这是共享的)。这可以防止使用多个连接对象并消除事务升级。您还可以通过简单地将连接对象从一个方法传递到另一个方法来实现此目的,但这并不那么干净,IMO。