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:
- Was bedeutet „Transaktionskontext wird von einer anderen Sitzung verwendet.“
- 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:
- Wir haben mehrere
SqlConnections
erstellt unter demselbenTransactionContext
(was bedeutet, dass sie sich auf dieselbe Transaktion beziehen) - Wir haben diese
SqlConnection
gefragt um gleichzeitig mit SQL Server zu kommunizieren - 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 istSerializable
aber in den meisten FällenReadCommitted
reicht; - Vergessen Sie nicht,
TransactionScope
zu vervollständigen() undDependentTransaction