Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Qual é o motivo do contexto de transação em uso por outra sessão


É um pouco tarde para responder :) mas espero que seja útil para outros. A resposta contém três partes:
  1. O que significa "Contexto de transação em uso por outra sessão?"
  2. Como reproduzir o erro "Contexto de transação em uso por outra sessão."

1. O que significa "Contexto de transação em uso por outra sessão."

Aviso importante:o bloqueio de contexto de transação é adquirido imediatamente antes e liberado imediatamente após a interação entre SqlConnection e SQL Server.

Quando você executa alguma consulta SQL, SqlConnection "looks" existe alguma transação envolvendo-o. Pode ser SqlTransaction ("nativo" para SqlConnection) ou Transaction de System.Transactions conjunto.

Quando a transação encontrou SqlConnection usa para se comunicar com o SQL Server e no momento eles comunicam Transaction contexto é exclusivamente bloqueado.

O que TransactionScope ? Ele cria Transaction e fornece informações sobre o .NET Framework Components, para que todos, incluindo SqlConnection, possam (e, por design, devem) usá-lo.

Então, declarando TransactionScope estamos criando uma nova transação que está disponível para todos os objetos "transacionáveis" instanciados no Thread atual .

Erro descrito significa o seguinte:
  1. Criamos várias SqlConnections sob o mesmo TransactionContext (o que significa que eles estão relacionados à mesma transação)
  2. Perguntamos a estes SqlConnection para se comunicar com o SQL Server simultaneamente
  3. Um deles bloqueou a Transaction atual contexto e o próximo erro lançado

2. Como reproduzir o erro "Contexto de transação em uso por outra sessão."

Em primeiro lugar, o contexto da transação é usado ("bloqueado") no momento da execução do comando sql. Portanto, é difícil reproduzir esse comportamento com certeza.

Mas podemos tentar fazer isso iniciando vários threads executando operações SQL relativamente longas em uma única transação. Vamos preparar a tabela [dbo].[Persons] em [tests] Base de dados:
USE [tests]
GO
DROP TABLE [dbo].[Persons]
GO
CREATE TABLE [dbo].[Persons](
    [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY,
    [Name] [nvarchar](1024) NOT NULL,
    [Nick] [nvarchar](1024) NOT NULL,
    [Email] [nvarchar](1024) NOT NULL)
GO
DECLARE @Counter INT
SET @Counter = 500

WHILE (@Counter > 0) BEGIN
    INSERT [dbo].[Persons] ([Name], [Nick], [Email])
    VALUES ('Sheev Palpatine', 'DarthSidious', '[email protected]')
    SET @Counter = @Counter - 1
END
GO

E reproduza "Contexto de transação em uso por outra sessão". erro com código C# baseado no exemplo de código Shrike
using System;
using System.Collections.Generic;
using System.Threading;
using System.Transactions;
using System.Data.SqlClient;

namespace SO.SQL.Transactions
{
    public static class TxContextInUseRepro
    {
        const int Iterations = 100;
        const int ThreadCount = 10;
        const int MaxThreadSleep = 50;
        const string ConnectionString = "Initial Catalog=tests;Data Source=.;" +
                                        "User ID=testUser;PWD=Qwerty12;";
        static readonly Random Rnd = new Random();
        public static void Main()
        {
            var txOptions = new TransactionOptions();
            txOptions.IsolationLevel = IsolationLevel.ReadCommitted;
            using (var ctx = new TransactionScope(
                TransactionScopeOption.Required, txOptions))
            {
                var current = Transaction.Current;
                DependentTransaction dtx = current.DependentClone(
                    DependentCloneOption.BlockCommitUntilComplete);               
                for (int i = 0; i < Iterations; i++)
                {
                    // make the transaction distributed
                    using (SqlConnection con1 = new SqlConnection(ConnectionString))
                    using (SqlConnection con2 = new SqlConnection(ConnectionString))
                    {
                        con1.Open();
                        con2.Open();
                    }

                    var threads = new List<Thread>();
                    for (int j = 0; j < ThreadCount; j++)
                    {
                        Thread t1 = new Thread(o => WorkCallback(dtx));
                        threads.Add(t1);
                        t1.Start();
                    }

                    for (int j = 0; j < ThreadCount; j++)
                        threads[j].Join();
                }
                dtx.Complete();
                ctx.Complete();
            }
        }

        private static void WorkCallback(DependentTransaction dtx)
        {
            using (var txScope1 = new TransactionScope(dtx))
            {
                using (SqlConnection con2 = new SqlConnection(ConnectionString))
                {
                    Thread.Sleep(Rnd.Next(MaxThreadSleep));
                    con2.Open();
                    using (var cmd = new SqlCommand("SELECT * FROM [dbo].[Persons]", con2))
                    using (cmd.ExecuteReader()) { } // simply recieve data
                }
                txScope1.Complete();
            }
        }
    }
}

E, para concluir, algumas palavras sobre a implementação do suporte a transações em seu aplicativo:
  • Evite operações de dados multithread se for possível (independentemente de carregar ou salvar). Por exemplo. salve SELECT /UPDATE / etc... solicitações em uma única fila e atende-as com um trabalhador de thread único;
  • Em aplicativos multithread, use transações. Sempre. Em toda parte. Mesmo para leitura;
  • Não compartilhe uma única transação entre vários tópicos. Causa estranhas, não óbvias, transcendentais e não reproduzíveis mensagens de erro:
    • "Contexto de transação em uso por outra sessão.":várias interações simultâneas com o servidor em uma transação;
    • "Tempo limite expirou. O período de tempo limite decorreu antes da conclusão da operação ou o servidor não está respondendo.":transações não dependentes foram concluídas;
    • "A transação está em dúvida.";
    • ... e presumo que muitas outras...
  • Não se esqueça de definir o nível de isolamento para TransactionScope . O padrão é Serializable mas na maioria dos casos ReadCommitted é suficiente;
  • Não se esqueça de Complete() TransactionScope e DependentTransaction