Trochę za późno na odpowiedź :), ale mam nadzieję, że przyda się innym. Odpowiedź składa się z trzech części:
- Co to znaczy „Kontekst transakcji używany przez inną sesję?”
- Jak odtworzyć błąd „Kontekst transakcji używany przez inną sesję”.
1. Co to znaczy „Kontekst transakcji używany przez inną sesję”.
Ważna uwaga:Blokada kontekstu transakcji jest pobierana tuż przed i zwalniana natychmiast po interakcji między SqlConnection
i SQL Server.
Kiedy wykonujesz jakieś zapytanie SQL, SqlConnection
"wygląda", czy jest jakaś transakcja, która go otacza. Może to być SqlTransaction
("native" dla SqlConnection) lub Transaction
z System.Transactions
montaż.
Po znalezieniu transakcji SqlConnection
używa go do komunikacji z SQL Serverem i w tej chwili komunikuje się Transaction
kontekst jest zablokowany na wyłączność.
Co oznacza TransactionScope
? Tworzy Transaction
i dostarcza informacji o komponentach .NET Framework, aby każdy, w tym SqlConnection, mógł (i powinien) z niego korzystać.
Więc deklarowanie TransactionScope
tworzymy nową transakcję, która jest dostępna dla wszystkich obiektów "transactable" utworzonych w bieżącym Thread
.
Opisany błąd oznacza:
- Utworzyliśmy kilka
SqlConnections
pod tym samymTransactionContext
(co oznacza, że dotyczyły tej samej transakcji) - Poprosiliśmy te
SqlConnection
komunikować się z SQL Server jednocześnie - Jeden z nich zablokował bieżącą
Transaction
kontekst, a następny wyrzucił błąd
2. Jak odtworzyć błąd „Kontekst transakcji używany przez inną sesję”.
Przede wszystkim kontekst transakcji jest używany ("zablokowany") w momencie wykonania polecenia sql. Więc na pewno trudno jest odtworzyć takie zachowanie.
Ale możemy spróbować to zrobić, uruchamiając wiele wątków wykonujących stosunkowo długie operacje SQL w ramach pojedynczej transakcji. Przygotujmy tabelę [dbo].[Persons]
w [tests]
Baza danych:
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
I odtwórz „Kontekst transakcji używany przez inną sesję”. błąd z kodem C# opartym na przykładzie kodu 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();
}
}
}
}
A na zakończenie kilka słów o wdrożeniu obsługi transakcji w Twojej aplikacji:
- Unikaj wielowątkowych operacji na danych, jeśli to możliwe (bez względu na ładowanie lub zapisywanie). Np. zapisz
SELECT
/UPDATE
/ etc... żądań w jednej kolejce i obsługuj je za pomocą jednowątkowego pracownika; - W aplikacjach wielowątkowych używaj transakcji. Zawsze. Wszędzie. Nawet do czytania;
- Nie udostępniaj pojedynczej transakcji między wieloma wątkami. Powoduje dziwne, nieoczywiste, transcendentalne i nieodtwarzalne komunikaty o błędach:
- "Kontekst transakcji używany przez inną sesję":wiele jednoczesnych interakcji z serwerem w ramach jednej transakcji;
- "Upłynął limit czasu. Limit czasu, który upłynął przed zakończeniem operacji lub serwer nie odpowiada.":zakończone zostały niezależne transakcje;
- "Transakcja jest wątpliwa.";
- ... i zakładam wiele innych...
- Nie zapomnij ustawić poziomu izolacji dla
TransactionScope
. Wartość domyślna toSerializable
ale w większości przypadkówReadCommitted
wystarczy; - Nie zapomnij o funkcji Complete()
TransactionScope
iDependentTransaction