Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Qual è il motivo del contesto della transazione in uso da un'altra sessione

È un po' tardi per la risposta :) ma spero che possa essere utile per gli altri. La risposta contiene tre parti:

  1. Che cosa significa "Contesto della transazione in uso da un'altra sessione."
  2. Come riprodurre l'errore "Contesto della transazione in uso da un'altra sessione."

1. Che cosa significa "Contesto della transazione in uso da un'altra sessione".

Avviso importante:il blocco del contesto della transazione viene acquisito appena prima e rilasciato immediatamente dopo l'interazione tra SqlConnection e SQL Server.

Quando esegui alcune query SQL, SqlConnection "sembra" c'è qualche transazione che lo avvolge. Potrebbe essere SqlTransaction ("nativo" per SqlConnection) o Transaction da System.Transactions assemblaggio.

Quando la transazione ha trovato SqlConnection lo usa per comunicare con SQL Server e nel momento in cui comunicano Transaction il contesto è bloccato esclusivamente.

Cosa significa TransactionScope ? Crea Transaction e fornisce informazioni sui componenti di .NET Framework a riguardo, in modo che tutti, incluso SqlConnection, possano (e in base alla progettazione dovrebbero) usarlo.

Quindi dichiarando TransactionScope stiamo creando una nuova Transazione che è disponibile per tutti gli oggetti "transattabili" istanziati nel Thread corrente .

L'errore descritto significa quanto segue:

  1. Abbiamo creato diverse SqlConnections sotto lo stesso TransactionContext (il che significa che si riferivano alla stessa transazione)
  2. Abbiamo chiesto questi SqlConnection per comunicare con SQL Server contemporaneamente
  3. Uno di loro ha bloccato la Transaction corrente contesto e il successivo errore generato

2. Come riprodurre l'errore "Contesto della transazione in uso da un'altra sessione."

Innanzitutto, il contesto della transazione viene utilizzato ("bloccato") proprio al momento dell'esecuzione del comando sql. Quindi è difficile riprodurre con certezza un comportamento del genere.

Ma possiamo provare a farlo avviando più thread eseguendo operazioni SQL relativamente lunghe nell'ambito della singola transazione. Prepariamo la tabella [dbo].[Persons] in [tests] Banca dati:

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 riprodurre "Contesto della transazione in uso da un'altra sessione". errore con codice C# basato sull'esempio di codice 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 in conclusione, alcune parole sull'implementazione del supporto alle transazioni nella tua applicazione:

  • Evita le operazioni di dati multi-thread, se possibile (indipendentemente dal caricamento o dal salvataggio). Per esempio. salva SELECT /UPDATE / ecc... richieste in un'unica coda e servirle con un singolo thread di lavoro;
  • Nelle applicazioni multi-thread utilizzare le transazioni. Sempre. Ovunque. Anche per la lettura;
  • Non condividere una singola transazione tra più thread. Provoca strano, non ovvio, trascendentale e non riproducibile messaggi di errore:
    • "Contesto della transazione in uso da un'altra sessione.":interazioni multiple simultanee con il server in una transazione;
    • "Timeout scaduto. Il periodo di timeout è trascorso prima del completamento dell'operazione o il server non risponde.":transazioni non dipendenti sono state completate;
    • "La transazione è in dubbio.";
    • ... e presumo molti altri...
  • Non dimenticare di impostare il livello di isolamento per TransactionScope . L'impostazione predefinita è Serializable ma nella maggior parte dei casi ReadCommitted è sufficiente;
  • Non dimenticare di completare() TransactionScope e DependentTransaction