Câu trả lời hơi muộn :) nhưng hy vọng nó sẽ hữu ích cho những người khác. Câu trả lời gồm ba phần:
- "Ngữ cảnh giao dịch được phiên khác sử dụng" có nghĩa là gì.
- Cách tạo lại lỗi "Bối cảnh giao dịch được một phiên sử dụng."
1. "Ngữ cảnh giao dịch được một phiên sử dụng" có nghĩa là gì.
Thông báo quan trọng:Khóa ngữ cảnh giao dịch được mua ngay trước đó và được giải phóng ngay sau khi tương tác giữa SqlConnection
và SQL Server.
Khi bạn thực thi một số Truy vấn SQL, SqlConnection
"có vẻ" là có bất kỳ giao dịch nào gói nó. Nó có thể là SqlTransaction
("gốc" cho SqlConnection) hoặc Giao dịch
từ System.Transactions
hội,, tổ hợp.
Khi giao dịch được tìm thấy SqlConnection
sử dụng nó để giao tiếp với SQL Server và tại thời điểm này chúng giao tiếp Giao dịch
ngữ cảnh bị khóa riêng.
TransactionScope
có chức năng gì ? Nó tạo ra Giao dịch
và cung cấp thông tin về Thành phần .NET Framework về nó, vì vậy tất cả mọi người bao gồm cả SqlConnection có thể (và theo thiết kế) nên sử dụng nó.
Vì vậy, khai báo TransactionScope
chúng tôi đang tạo Giao dịch mới có sẵn cho tất cả các đối tượng "có thể giao dịch" được khởi tạo trong Luồng
hiện tại .
Lỗi được mô tả có nghĩa như sau:
- Chúng tôi đã tạo một số
SqlConnections
trong cùng mộtTransactionContext
(có nghĩa là chúng liên quan đến cùng một giao dịch) - Chúng tôi đã hỏi những
SqlConnection
này để giao tiếp với SQL Server đồng thời - Một trong số họ đã khóa
Giao dịch
hiện tại ngữ cảnh và một lỗi tiếp theo được ném ra
2. Cách tạo lại lỗi "Ngữ cảnh giao dịch được phiên khác sử dụng."
Trước hết, bối cảnh giao dịch được sử dụng ("bị khóa") ngay tại thời điểm thực hiện lệnh sql. Vì vậy, rất khó để tạo lại một hành vi như vậy chắc chắn.
Nhưng chúng ta có thể thử làm điều đó bằng cách bắt đầu nhiều luồng chạy các hoạt động SQL tương đối dài trong một giao dịch duy nhất. trong [tests]
Cơ sở dữ liệu:
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
Và tái tạo "Ngữ cảnh giao dịch được sử dụng bởi một phiên khác." lỗi với mã C # dựa trên ví dụ về mã 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();
}
}
}
}
Và kết luận một vài từ về việc triển khai hỗ trợ giao dịch trong ứng dụng của bạn:
- Tránh các thao tác dữ liệu đa luồng nếu có thể (bất kể đang tải hay lưu). Ví dụ. lưu
SELECT
/CẬP NHẬT
/ etc ... yêu cầu trong một hàng đợi và phân phát chúng với một nhân viên đơn luồng; - Trong các ứng dụng đa luồng, sử dụng các giao dịch. Luôn luôn. Mọi nơi. Ngay cả để đọc;
- Không chia sẻ giao dịch đơn lẻ giữa nhiều chuỗi. Nó gây ra sự kỳ lạ, không rõ ràng, siêu việt và không thể tái tạo thông báo lỗi:
- "Bối cảnh giao dịch được một phiên sử dụng.":nhiều tương tác đồng thời với máy chủ trong một giao dịch;
- "Hết thời gian chờ. Khoảng thời gian chờ đã trôi qua trước khi hoàn thành hoạt động hoặc máy chủ không phản hồi.":không phải giao dịch phụ thuộc đã được hoàn thành;
- "Giao dịch đang bị nghi ngờ.";
- ... và tôi giả định rất nhiều điều khác ...
- Đừng quên đặt Mức cách ly cho
TransactionScope
. Mặc định làSerializable
nhưng trong hầu hết các trường hợp,ReadComiled
là đủ; - Đừng quên Hoàn thành ()
TransactionScope
vàDependentTransaction