检测查询是否在分布式事务中

时间:2018-07-03 23:22:26

标签: sql-server distributed-transactions

我需要一种可靠的方法来从SQL Server查询中确定查询是否在分布式事务中运行。 1 分布式事务是在外部创建还是使用它创建都无关紧要BEGIN DISTRIBUTED TRANSACTION声明-无论哪种方式,我都需要了解。

我看不到有特定的SQL Server functionstored procedure声称提供此信息。有一些dynamic-management views的文档声明将提供此信息,但是该信息不可靠。例如,sys.dm_tran_session_transactions的列为is_local

  

1 =本地交易。

     

0 =分布式事务或登记绑定会话事务。

因此,请使用is unsupported in a distributed transaction并使用SAVE TRANSACTION进行测试,它将导致错误。 2

此查询不在分布式事务中,并且按预期运行,请为is_local选择值1:

BEGIN TRANSACTION

SELECT s.is_local
FROM   sys.dm_tran_session_transactions s

SAVE TRANSACTION Error

ROLLBACK TRANSACTION

但是,如果我们将BEGIN TRANSACTION替换为BEGIN DISTRIBUTED TRANSACTION,则is_local仍为1,但是会出现错误“无法在分布式事务中使用SAVE TRANSACTION”。因此,我们不能依赖is_local值。

sys.dm_tran_active_transactions怎么样?其transaction_type列描述如下:

  

交易类型。

     

1 =读/写事务

     

2 =只读事务

     

3 =系统交易

     

4 =分布式交易

我们还需要一种方法来标识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();
            }
        }
    }
}

结果: Output from C# code

取消注释SAVE TRANSACTION会执行相同的操作,但有例外,这与预期的一样,表明已分配事务。较早的is_local可以进行类似的测试。再一次,is_localtransaction_type都不可靠地表示分布式事务。

我一直无法找到另一种记录的方法来尝试检测SQL中的分布式事务。可能吗?如果可以,怎么办?

¹此问题与.net detect distributed transaction表面上有关,但是我需要从SQL而不是.NET进行检测。

²我需要在不引起错误的情况下检测分布式事务,因此我不能只将SAVE TRANSACTION放在查询中并等待错误。

1 个答案:

答案 0 :(得分:1)

到目前为止,我发现的最佳解决方案是检查sys.dm_tran_active_transactions视图上的其他字段。 Documentation描述了列transaction_uow

  

分布式事务的事务工作单元(UOW)标识符。 MS DTC使用UOW标识符来处理分布式事务。

对于我发现的每种情况,当我们处于分布式事务中时,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

结果:

Results from distributed-query check

修改问题中的C#代码以测试分布式事务时,行为是相同的。