检测查询是否在分布式事务中
Detect whether a query is in a distributed transaction
我需要一种可靠的方法来确定 SQL 服务器查询中的查询是否 运行 在分布式事务中。1 它分布式事务是在外部创建的还是使用 BEGIN DISTRIBUTED TRANSACTION
语句创建的并不重要 — 无论哪种方式,我都需要了解它。
我没有看到特定的 SQL Server function or stored procedure claiming to provide this information. There are some dynamic-management views whose documentation claims will provide this information, but the information is unreliable. For example, sys.dm_tran_session_transactions
有列 is_local
:
1 = Local transaction.
0 = Distributed transaction or an enlisted bound session transaction.
所以,测试一下,利用SAVE TRANSACTION
,is unsupported in a distributed transaction,会报错。2
此查询不在分布式事务中并按预期工作,为 is_local
选择值 1:
BEGIN TRANSACTION
SELECT s.is_local
FROM sys.dm_tran_session_transactions s
SAVE TRANSACTION Error
ROLLBACK TRANSACTION
但是,如果我们用 BEGIN DISTRIBUTED TRANSACTION
替换 BEGIN TRANSACTION
,is_local
仍然是 1,但是我们得到错误 "Cannot use SAVE TRANSACTION within a distributed transaction." 所以,我们不能依赖 is_local
值。
sys.dm_tran_active_transactions
怎么样?它的 transaction_type
列描述为:
Type of transaction.
1 = Read/write transaction
2 = Read-only transaction
3 = System transaction
4 = Distributed transaction
我们还需要一种方法来识别当前交易,sys.dm_tran_current_transaction
提供了这种方法。那么,让我们再测试一下:
BEGIN TRANSACTION
SELECT a.transaction_type
FROM sys.dm_tran_current_transaction c
INNER JOIN sys.dm_tran_active_transactions a ON c.transaction_id = a.transaction_id
SAVE TRANSACTION Error
ROLLBACK TRANSACTION
对于此非分布式事务,我们得到值 1,但也可能是 2。但是,再次将 BEGIN TRANSACTION
替换为 BEGIN DISTRIBUTED TRANSACTION
,我们会得到与 transaction_type
相同的值,但这次是 SAVE TRANSACTION
的错误。所以,我们也不能依赖transaction_type
。
为了确保问题不是分布式事务中的实际登记,我还尝试使用 C# 代码中的 TransactionScope
。
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Transactions;
using IsolationLevel = System.Transactions.IsolationLevel;
namespace TransactionTroubleshooting
{
class Program
{
private const string ConnectionString = "Server=.;Database=master;Trusted_Connection=True;";
// Use C# 7.1 or later.
public static async Task Main(string[] args)
{
try
{
await RunOuterTransaction();
}
catch (Exception e)
{
var current = e;
while (current != null)
{
Console.WriteLine(current.Message);
Console.WriteLine();
Console.WriteLine(current.StackTrace);
Console.WriteLine();
current = current.InnerException;
}
}
finally
{
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
private static async Task RunOuterTransaction()
{
using (var transaction = new TransactionScope(TransactionScopeOption.RequiresNew,
new TransactionOptions {IsolationLevel = IsolationLevel.ReadCommitted},
TransactionScopeAsyncFlowOption.Enabled))
using (var connection = new SqlConnection(ConnectionString))
{
await connection.OpenAsync();
using (var command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = @"
SELECT a.transaction_type
FROM sys.dm_tran_current_transaction c
INNER JOIN sys.dm_tran_active_transactions a ON c.transaction_id = a.transaction_id
";
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
Console.WriteLine("Outer transaction_type is {0}", reader["transaction_type"]);
}
}
}
await RunInnerTransaction();
transaction.Complete();
}
}
private static async Task RunInnerTransaction()
{
// We need Required, not RequiresNew, to get the distributed transaction.
using (var transaction = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
TransactionScopeAsyncFlowOption.Enabled))
using (var connection = new SqlConnection(ConnectionString))
{
await connection.OpenAsync();
using (var command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = @"
SELECT a.transaction_type
FROM sys.dm_tran_current_transaction c
INNER JOIN sys.dm_tran_active_transactions a ON c.transaction_id = a.transaction_id
-- Because this query is in a distributed transaction, if you want to throw, uncomment:
-- SAVE TRANSACTION Error
";
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
Console.WriteLine("Inner transaction_type is {0}", reader["transaction_type"]);
}
}
}
transaction.Complete();
}
}
}
}
结果:
取消注释 SAVE TRANSACTION
与预期的添加异常一样,表示分布式事务。前面的 is_local
可以类似地测试。再一次,is_local
和 transaction_type
都不能可靠地指示分布式事务。
我一直无法找到另一种记录在案的方法来尝试在 SQL 中检测分布式事务。可能吗?如果可以,怎么做?
¹ 这个问题表面上与 .net detect distributed transaction 相关,但我需要从 SQL 而不是 .NET 进行检测。
² 我需要在不引起错误的情况下检测分布式事务,所以我不能只在查询中放入 SAVE TRANSACTION
并等待错误。
到目前为止,我找到的最佳解决方案是检查 sys.dm_tran_active_transactions
视图中的不同字段。 Documentation 描述列 transaction_uow
:
Transaction unit of work (UOW) identifier for distributed transactions. MS DTC uses the UOW identifier to work with the distributed transaction.
对于我发现的每个案例,当我们在分布式事务中时,transaction_uow
是非空的;否则,transaction_uow
为空。下面SQL演示:
BEGIN TRANSACTION
SELECT IIF(a.transaction_uow IS NULL, N'Not Distributed', N'Distributed') AS [Distributed?]
FROM sys.dm_tran_current_transaction c
INNER JOIN sys.dm_tran_active_transactions a ON c.transaction_id = a.transaction_id
ROLLBACK TRANSACTION
BEGIN DISTRIBUTED TRANSACTION
SELECT IIF(a.transaction_uow IS NULL, N'Not Distributed', N'Distributed') AS [Distributed?]
FROM sys.dm_tran_current_transaction c
INNER JOIN sys.dm_tran_active_transactions a ON c.transaction_id = a.transaction_id
ROLLBACK TRANSACTION
结果:
修改问题中的 C# 代码以测试分布式事务时的行为相同。
我需要一种可靠的方法来确定 SQL 服务器查询中的查询是否 运行 在分布式事务中。1 它分布式事务是在外部创建的还是使用 BEGIN DISTRIBUTED TRANSACTION
语句创建的并不重要 — 无论哪种方式,我都需要了解它。
我没有看到特定的 SQL Server function or stored procedure claiming to provide this information. There are some dynamic-management views whose documentation claims will provide this information, but the information is unreliable. For example, sys.dm_tran_session_transactions
有列 is_local
:
1 = Local transaction.
0 = Distributed transaction or an enlisted bound session transaction.
所以,测试一下,利用SAVE TRANSACTION
,is unsupported in a distributed transaction,会报错。2
此查询不在分布式事务中并按预期工作,为 is_local
选择值 1:
BEGIN TRANSACTION
SELECT s.is_local
FROM sys.dm_tran_session_transactions s
SAVE TRANSACTION Error
ROLLBACK TRANSACTION
但是,如果我们用 BEGIN DISTRIBUTED TRANSACTION
替换 BEGIN TRANSACTION
,is_local
仍然是 1,但是我们得到错误 "Cannot use SAVE TRANSACTION within a distributed transaction." 所以,我们不能依赖 is_local
值。
sys.dm_tran_active_transactions
怎么样?它的 transaction_type
列描述为:
Type of transaction.
1 = Read/write transaction
2 = Read-only transaction
3 = System transaction
4 = Distributed transaction
我们还需要一种方法来识别当前交易,sys.dm_tran_current_transaction
提供了这种方法。那么,让我们再测试一下:
BEGIN TRANSACTION
SELECT a.transaction_type
FROM sys.dm_tran_current_transaction c
INNER JOIN sys.dm_tran_active_transactions a ON c.transaction_id = a.transaction_id
SAVE TRANSACTION Error
ROLLBACK TRANSACTION
对于此非分布式事务,我们得到值 1,但也可能是 2。但是,再次将 BEGIN TRANSACTION
替换为 BEGIN DISTRIBUTED TRANSACTION
,我们会得到与 transaction_type
相同的值,但这次是 SAVE TRANSACTION
的错误。所以,我们也不能依赖transaction_type
。
为了确保问题不是分布式事务中的实际登记,我还尝试使用 C# 代码中的 TransactionScope
。
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Transactions;
using IsolationLevel = System.Transactions.IsolationLevel;
namespace TransactionTroubleshooting
{
class Program
{
private const string ConnectionString = "Server=.;Database=master;Trusted_Connection=True;";
// Use C# 7.1 or later.
public static async Task Main(string[] args)
{
try
{
await RunOuterTransaction();
}
catch (Exception e)
{
var current = e;
while (current != null)
{
Console.WriteLine(current.Message);
Console.WriteLine();
Console.WriteLine(current.StackTrace);
Console.WriteLine();
current = current.InnerException;
}
}
finally
{
Console.WriteLine("Press a key...");
Console.ReadKey();
}
}
private static async Task RunOuterTransaction()
{
using (var transaction = new TransactionScope(TransactionScopeOption.RequiresNew,
new TransactionOptions {IsolationLevel = IsolationLevel.ReadCommitted},
TransactionScopeAsyncFlowOption.Enabled))
using (var connection = new SqlConnection(ConnectionString))
{
await connection.OpenAsync();
using (var command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = @"
SELECT a.transaction_type
FROM sys.dm_tran_current_transaction c
INNER JOIN sys.dm_tran_active_transactions a ON c.transaction_id = a.transaction_id
";
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
Console.WriteLine("Outer transaction_type is {0}", reader["transaction_type"]);
}
}
}
await RunInnerTransaction();
transaction.Complete();
}
}
private static async Task RunInnerTransaction()
{
// We need Required, not RequiresNew, to get the distributed transaction.
using (var transaction = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
TransactionScopeAsyncFlowOption.Enabled))
using (var connection = new SqlConnection(ConnectionString))
{
await connection.OpenAsync();
using (var command = connection.CreateCommand())
{
command.CommandType = CommandType.Text;
command.CommandText = @"
SELECT a.transaction_type
FROM sys.dm_tran_current_transaction c
INNER JOIN sys.dm_tran_active_transactions a ON c.transaction_id = a.transaction_id
-- Because this query is in a distributed transaction, if you want to throw, uncomment:
-- SAVE TRANSACTION Error
";
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
Console.WriteLine("Inner transaction_type is {0}", reader["transaction_type"]);
}
}
}
transaction.Complete();
}
}
}
}
结果:
取消注释 SAVE TRANSACTION
与预期的添加异常一样,表示分布式事务。前面的 is_local
可以类似地测试。再一次,is_local
和 transaction_type
都不能可靠地指示分布式事务。
我一直无法找到另一种记录在案的方法来尝试在 SQL 中检测分布式事务。可能吗?如果可以,怎么做?
¹ 这个问题表面上与 .net detect distributed transaction 相关,但我需要从 SQL 而不是 .NET 进行检测。
² 我需要在不引起错误的情况下检测分布式事务,所以我不能只在查询中放入 SAVE TRANSACTION
并等待错误。
到目前为止,我找到的最佳解决方案是检查 sys.dm_tran_active_transactions
视图中的不同字段。 Documentation 描述列 transaction_uow
:
Transaction unit of work (UOW) identifier for distributed transactions. MS DTC uses the UOW identifier to work with the distributed transaction.
对于我发现的每个案例,当我们在分布式事务中时,transaction_uow
是非空的;否则,transaction_uow
为空。下面SQL演示:
BEGIN TRANSACTION
SELECT IIF(a.transaction_uow IS NULL, N'Not Distributed', N'Distributed') AS [Distributed?]
FROM sys.dm_tran_current_transaction c
INNER JOIN sys.dm_tran_active_transactions a ON c.transaction_id = a.transaction_id
ROLLBACK TRANSACTION
BEGIN DISTRIBUTED TRANSACTION
SELECT IIF(a.transaction_uow IS NULL, N'Not Distributed', N'Distributed') AS [Distributed?]
FROM sys.dm_tran_current_transaction c
INNER JOIN sys.dm_tran_active_transactions a ON c.transaction_id = a.transaction_id
ROLLBACK TRANSACTION
结果:
修改问题中的 C# 代码以测试分布式事务时的行为相同。