Agak terlambat menjawabnya :) tapi semoga bermanfaat bagi orang lain. Jawaban terdiri dari tiga bagian:
- Apa artinya "Konteks transaksi sedang digunakan oleh sesi lain".
- Cara mereproduksi kesalahan "Konteks transaksi sedang digunakan oleh sesi lain."
1. Apa artinya "Konteks transaksi sedang digunakan oleh sesi lain".
Pemberitahuan penting:Kunci konteks transaksi diperoleh sebelum dan segera dirilis setelah interaksi antara SqlConnection
dan SQLServer.
Saat Anda menjalankan beberapa SQL Query, SqlConnection
"terlihat" apakah ada transaksi yang membungkusnya. Mungkin SqlTransaction
("asli" untuk SqlConnection) atau Transaction
dari System.Transactions
perakitan.
Saat transaksi ditemukan SqlConnection
menggunakannya untuk berkomunikasi dengan SQL Server dan saat ini mereka berkomunikasi Transaction
konteks dikunci secara eksklusif.
Apa yang dimaksud dengan TransactionScope
? Itu menciptakan Transaction
dan memberikan informasi .NET Framework Components tentangnya, sehingga semua orang termasuk SqlConnection dapat (dan sesuai desain) menggunakannya.
Jadi mendeklarasikan TransactionScope
kami membuat Transaksi baru yang tersedia untuk semua objek "yang dapat ditransaksikan" yang dibuat dalam Thread
saat ini .
Kesalahan yang dijelaskan berarti sebagai berikut:
- Kami membuat beberapa
SqlConnections
di bawahTransactionContext
yang sama (yang berarti mereka terkait dengan transaksi yang sama) - Kami menanyakan
SqlConnection
ini untuk berkomunikasi dengan SQL Server secara bersamaan - Salah satunya mengunci
Transaction
current konteks dan kesalahan berikutnya
2. Cara mereproduksi kesalahan "Konteks transaksi sedang digunakan oleh sesi lain."
Pertama-tama, konteks transaksi digunakan ("dikunci") tepat pada saat eksekusi perintah sql. Jadi pasti sulit untuk mereproduksi perilaku seperti itu.
Tetapi kita dapat mencoba melakukannya dengan memulai beberapa utas yang menjalankan operasi SQL yang relatif lama di bawah satu transaksi. Mari kita siapkan tabel [dbo].[Persons]
di [tests]
Basis Data:
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
Dan mereproduksi "Konteks transaksi sedang digunakan oleh sesi lain." kesalahan dengan kode C# berdasarkan contoh kode 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();
}
}
}
}
Dan sebagai kesimpulan, beberapa kata tentang penerapan dukungan transaksi di aplikasi Anda:
- Hindari operasi data multi-utas jika memungkinkan (tidak masalah memuat atau menyimpan). Misalnya. simpan
SELECT
/UPDATE
/ etc... permintaan dalam satu antrean dan melayaninya dengan pekerja utas tunggal; - Dalam aplikasi multi-utas gunakan transaksi. Selalu. Di mana pun. Bahkan untuk membaca;
- Jangan bagikan satu transaksi di antara banyak utas. Itu menyebabkan aneh, tidak jelas, transendental dan tidak dapat direproduksi pesan kesalahan:
- "Konteks transaksi sedang digunakan oleh sesi lain.":beberapa interaksi simultan dengan server dalam satu transaksi;
- "Waktu habis kedaluwarsa. Jangka waktu habis sebelum operasi selesai atau server tidak merespons.":transaksi tidak tergantung selesai;
- "Transaksi diragukan.";
- ... dan saya berasumsi banyak lainnya ...
- Jangan lupa set Isolation Level untuk
TransactionScope
. Standarnya adalahSerializable
tetapi dalam kebanyakan kasusReadCommitted
cukup; - Jangan lupa untuk Complete()
TransactionScope
danDependentTransaction