Мне нужен надежный способ определить из запроса 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();
}
}
}
}
Раскомментирование SAVE TRANSACTION
делает то же самое с добавленным исключением, как и ожидалось, указывающим на распределенную транзакцию. Аналогично можно протестировать is_local
ранее. И снова ни is_local
, ни transaction_type
не указывают на распределенную транзакцию.
Мне не удалось найти другой задокументированный способ обнаружения распределенной транзакции в SQL. Является ли это возможным? Если да, то как?
¹ Этот вопрос поверхностно связан с .net обнаруживает распределенную транзакцию, но мне нужно обнаружение с помощью SQL, а не .NET.
² Мне нужно обнаружить распределенную транзакцию, не вызывая ошибки, поэтому я не могу просто поставить SAVE TRANSACTION
в свой запрос и ждать ошибки.