Определить, находится ли запрос в распределенной транзакции

Мне нужен надежный способ определить из запроса SQL Server, выполняется ли запрос внутри распределенной транзакции.1 Неважно, была ли распределенная транзакция создана извне или с помощью оператора BEGIN DISTRIBUTED TRANSACTION в любом случае, мне нужно знать об этом.

Я не вижу конкретного функция SQL Server или хранимая процедура, утверждающая, что предоставила эту информацию. Есть несколько представления динамического управления, чьи заявления в документации предоставляют эту информацию, но эта информация ненадежна. Например, sys.dm_tran_session_transactions имеет столбец is_local:

1 = локальная транзакция.

0 = распределенная транзакция или прикрепленная транзакция связанного сеанса.

Итак, протестируйте его, используя SAVE TRANSACTION, который не поддерживается в распределенной транзакции и вызовет ошибку.2

Этот запрос не является распределенной транзакцией и работает должным образом, выбирая значение 1 для is_local:

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.

Просто чтобы убедиться, что проблема не в фактическом включении в распределенную транзакцию, я также попытался использовать TransactionScope из кода C#.

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();
            }
        }
    }
}

Результаты: Вывод из кода C#

Раскомментирование SAVE TRANSACTION делает то же самое с добавленным исключением, как и ожидалось, указывающим на распределенную транзакцию. Аналогично можно протестировать is_local ранее. И снова ни is_local, ни transaction_type не указывают на распределенную транзакцию.

Мне не удалось найти другой задокументированный способ обнаружения распределенной транзакции в SQL. Является ли это возможным? Если да, то как?

¹ Этот вопрос поверхностно связан с .net обнаруживает распределенную транзакцию, но мне нужно обнаружение с помощью SQL, а не .NET.

² Мне нужно обнаружить распределенную транзакцию, не вызывая ошибки, поэтому я не могу просто поставить SAVE TRANSACTION в свой запрос и ждать ошибки.


person Andrew    schedule 03.07.2018    source источник


Ответы (1)


Лучшее решение, которое я нашел до сих пор, — это проверить другое поле в представлении sys.dm_tran_active_transactions. Документация описывает столбец 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# в вопросе для проверки распределенной транзакции.

person Andrew    schedule 03.07.2018