Het is een beetje laat voor antwoord :) maar ik hoop dat het nuttig zal zijn voor anderen.Antwoord bestaat uit drie delen:
- Wat betekent het "Transactiecontext in gebruik door een andere sessie."
- De fout 'Transactiecontext in gebruik door een andere sessie' reproduceren.
Belangrijke opmerking:Transactiecontextvergrendeling wordt vlak voor verkregen en onmiddellijk vrijgegeven na interactie tussen SqlConnection
en SQL-server.
Wanneer u een SQL-query uitvoert, SqlConnection
"looks" is er een transactie die het inpakt. Het kan SqlTransaction
zijn ("native" voor SqlConnection) of Transaction
van System.Transactions
bijeenkomst.
Wanneer transactie gevonden SqlConnection
gebruikt het om te communiceren met SQL Server en op het moment dat ze communiceren Transaction
context is exclusief vergrendeld.
Wat doet TransactionScope
? Het creëert Transaction
en biedt informatie over .NET Framework Components erover, zodat iedereen, inclusief SqlConnection, het kan (en zou moeten) gebruiken.
Dus verklaren TransactionScope
we creëren een nieuwe transactie die beschikbaar is voor alle "verhandelbare" objecten die zijn geïnstantieerd in de huidige Thread
.
Beschreven fout betekent het volgende:
- We hebben verschillende
SqlConnections
gemaakt onder dezelfdeTransactionContext
(wat betekent dat ze betrekking hadden op dezelfde transactie) - We vroegen deze
SqlConnection
gelijktijdig communiceren met SQL Server - Een van hen heeft de huidige
Transaction
vergrendeld context en volgende fout gegooid
Allereerst wordt de transactiecontext gebruikt ("vergrendeld") op het moment dat de sql-opdracht wordt uitgevoerd. Het is dus moeilijk om dergelijk gedrag zeker te reproduceren.
Maar we kunnen het proberen door meerdere threads te starten die relatief lange SQL-bewerkingen uitvoeren onder de enkele transactie. Laten we de tabel [dbo].[Persons]
voorbereiden. in [tests]
Database:
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
En reproduceer 'Transactiecontext in gebruik door een andere sessie'. fout met C#-code op basis van Shrike-codevoorbeeld
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();
}
}
}
}
En tot slot een paar woorden over het implementeren van transactieondersteuning in uw applicatie:
- Vermijd multi-threaded databewerkingen als dat mogelijk is (ongeacht laden of opslaan). bijv. bewaar
SELECT
/UPDATE
/ etc... verzoeken in een enkele wachtrij en serveer ze met een single-thread worker; - Gebruik transacties in toepassingen met meerdere threads. Altijd. Overal. Zelfs om te lezen;
- Deel geen enkele transactie tussen meerdere threads. Het veroorzaakt vreemde, onopvallende, transcendentale en niet reproduceerbare foutmeldingen:
- "Transactiecontext in gebruik door een andere sessie.":meerdere gelijktijdige interacties met de server onder één transactie;
- "Time-out verlopen. De time-outperiode is verstreken voordat de bewerking is voltooid of de server reageert niet.":niet-afhankelijke transacties zijn voltooid;
- "De transactie is in twijfel.";
- ... en ik neem aan dat veel andere ...
- Vergeet niet het isolatieniveau in te stellen voor
TransactionScope
. Standaard isSerializable
maar in de meeste gevallenReadCommitted
is genoeg; - Vergeet niet om
TransactionScope
te voltooien() enDependentTransaction