Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Was ist der Grund dafür, dass der Transaktionskontext von einer anderen Sitzung verwendet wird?

Es ist ein bisschen spät für die Antwort :) aber ich hoffe, es wird für andere nützlich sein. Die Antwort besteht aus drei Teilen:

  1. Was bedeutet „Transaktionskontext wird von einer anderen Sitzung verwendet.“
  2. So reproduzieren Sie den Fehler "Transaktionskontext wird von einer anderen Sitzung verwendet."

1. Was bedeutet "Transaktionskontext wird von einer anderen Sitzung verwendet."

Wichtiger Hinweis:Die Transaktionskontextsperre wird unmittelbar vor der Interaktion zwischen SqlConnection erworben und unmittelbar nach der Interaktion aufgehoben und SQL-Server.

Wenn Sie eine SQL-Abfrage ausführen, SqlConnection "Looks" gibt es eine Transaktionsverpackung. Dies kann SqlTransaction sein ("native" für SqlConnection) oder Transaction aus System.Transactions Montage.

Wenn die Transaktion SqlConnection gefunden hat verwendet es, um mit SQL Server zu kommunizieren, und im Moment kommunizieren sie Transaction Kontext ist exklusiv gesperrt.

Was bedeutet TransactionScope ? Es erstellt Transaction und stellt .NET Framework-Komponenteninformationen darüber bereit, sodass jeder, einschließlich SqlConnection, es verwenden kann (und sollte).

Deklarieren Sie also TransactionScope Wir erstellen eine neue Transaktion, die für alle "transaktionsfähigen" Objekte verfügbar ist, die im aktuellen Thread instanziiert werden .

Beschriebener Fehler bedeutet Folgendes:

  1. Wir haben mehrere SqlConnections erstellt unter demselben TransactionContext (was bedeutet, dass sie sich auf dieselbe Transaktion beziehen)
  2. Wir haben diese SqlConnection gefragt um gleichzeitig mit SQL Server zu kommunizieren
  3. Einer von ihnen hat die aktuelle Transaction gesperrt Kontext und der nächste geworfene Fehler

2. So reproduzieren Sie den Fehler "Transaktionskontext wird von einer anderen Sitzung verwendet."

Zunächst einmal wird der Transaktionskontext direkt zum Zeitpunkt der Ausführung des SQL-Befehls verwendet ("gesperrt"). Daher ist es schwierig, ein solches Verhalten sicher zu reproduzieren.

Aber wir können es versuchen, indem wir mehrere Threads starten, die relativ lange SQL-Operationen unter der einzelnen Transaktion ausführen. Lassen Sie uns die Tabelle [dbo].[Persons] vorbereiten in [tests] Datenbank:

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

Und reproduzieren Sie "Transaktionskontext wird von einer anderen Sitzung verwendet". Fehler mit C#-Code basierend auf Shrike-Codebeispiel

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

Und zum Schluss noch ein paar Worte zur Implementierung von Transaktionsunterstützung in Ihrer Anwendung:

  • Vermeiden Sie Datenoperationen mit mehreren Threads, wenn dies möglich ist (unabhängig vom Laden oder Speichern). Z.B. SELECT speichern /UPDATE /etc... Anfragen in einer einzigen Warteschlange und bedient sie mit einem Single-Thread-Worker;
  • Verwenden Sie in Multithread-Anwendungen Transaktionen. Stets. Überall, überallhin, allerorts. Auch zum Lesen;
  • Teilen Sie keine einzelne Transaktion zwischen mehreren Threads. Es verursacht seltsame, nicht offensichtliche, transzendente und nicht reproduzierbare Fehlermeldungen:
    • "Transaktionskontext wird von einer anderen Sitzung verwendet.":mehrere gleichzeitige Interaktionen mit dem Server unter einer Transaktion;
    • "Zeitüberschreitung abgelaufen. Die Zeitüberschreitung ist abgelaufen, bevor der Vorgang abgeschlossen wurde, oder der Server antwortet nicht.":nicht abhängige Transaktionen wurden abgeschlossen;
    • "Die Transaktion ist zweifelhaft.";
    • ... und ich nehme an, viele andere ...
  • Vergessen Sie nicht, die Isolationsstufe für TransactionScope festzulegen . Standard ist Serializable aber in den meisten Fällen ReadCommitted reicht;
  • Vergessen Sie nicht, TransactionScope zu vervollständigen() und DependentTransaction