【问题标题】:Detect whether a query is in a distributed transaction检测查询是否在分布式事务中
【发布时间】:2018-12-12 07:43:34
【问题描述】:

我需要一种可靠的方法来从 SQL Server 查询中确定查询是否在分布式事务中运行。1 分布式事务是在外部创建还是使用BEGIN DISTRIBUTED TRANSACTION 声明——不管怎样,我需要知道它。

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

1 = 本地事务。

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

所以,测试一下,使用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 TRANSACTION 替换为 BEGIN DISTRIBUTED TRANSACTIONis_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();
            }
        }
    }
}

结果:

取消注释SAVE TRANSACTION 执行相同的操作,但添加了一个异常,正如预期的那样,表示分布式事务。之前的is_local 可以进行类似的测试。同样,is_localtransaction_type 都不能可靠地表明分布式事务。

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

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

² 我需要在不导致错误的情况下检测分布式事务,所以我不能只在查询中输入SAVE TRANSACTION 并等待错误。

【问题讨论】:

    标签: sql-server distributed-transactions


    【解决方案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
    

    结果:

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

    【讨论】:

      猜你喜欢
      • 2011-05-15
      • 1970-01-01
      • 1970-01-01
      • 2010-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多