Trong trường hợp nào, một SqlConnection sẽ tự động được ghi danh vào một Giao dịch Giao dịch Kính viễn vọng?


201

Điều đó có nghĩa là gì khi một SqlConnection được "nhập ngũ" trong một giao dịch? Có phải nó chỉ đơn giản có nghĩa là các lệnh tôi thực hiện trên kết nối sẽ tham gia vào giao dịch?

Nếu vậy, trong trường hợp nào, một SqlConnection sẽ tự động được ghi danh vào một Giao dịch Giao dịch Kính viễn vọng xung quanh?

Xem câu hỏi trong ý kiến ​​mã. Tôi đoán câu trả lời của mỗi câu hỏi theo sau mỗi câu hỏi trong ngoặc đơn.

Kịch bản 1: Mở kết nối TRONG phạm vi giao dịch

using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = ConnectToDB())
{   
    // Q1: Is connection automatically enlisted in transaction? (Yes?)
    //
    // Q2: If I open (and run commands on) a second connection now,
    // with an identical connection string,
    // what, if any, is the relationship of this second connection to the first?
    //
    // Q3: Will this second connection's automatic enlistment
    // in the current transaction scope cause the transaction to be
    // escalated to a distributed transaction? (Yes?)
}

Kịch bản 2: Sử dụng các kết nối TRONG phạm vi giao dịch đã được mở NGOÀI TRỜI

//Assume no ambient transaction active now
SqlConnection new_or_existing_connection = ConnectToDB(); //or passed in as method parameter
using (TransactionScope scope = new TransactionScope())
{
    // Connection was opened before transaction scope was created
    // Q4: If I start executing commands on the connection now,
    // will it automatically become enlisted in the current transaction scope? (No?)
    //
    // Q5: If not enlisted, will commands I execute on the connection now
    // participate in the ambient transaction? (No?)
    //
    // Q6: If commands on this connection are
    // not participating in the current transaction, will they be committed
    // even if rollback the current transaction scope? (Yes?)
    //
    // If my thoughts are correct, all of the above is disturbing,
    // because it would look like I'm executing commands
    // in a transaction scope, when in fact I'm not at all, 
    // until I do the following...
    //
    // Now enlisting existing connection in current transaction
    conn.EnlistTransaction( Transaction.Current );
    //
    // Q7: Does the above method explicitly enlist the pre-existing connection
    // in the current ambient transaction, so that commands I
    // execute on the connection now participate in the
    // ambient transaction? (Yes?)
    //
    // Q8: If the existing connection was already enlisted in a transaction
    // when I called the above method, what would happen?  Might an error be thrown? (Probably?)
    //
    // Q9: If the existing connection was already enlisted in a transaction
    // and I did NOT call the above method to enlist it, would any commands
    // I execute on it participate in it's existing transaction rather than
    // the current transaction scope. (Yes?)
}

Câu trả lời:


188

Tôi đã thực hiện một số bài kiểm tra kể từ khi hỏi câu hỏi này và tìm thấy hầu hết nếu không phải tất cả các câu trả lời, vì không ai trả lời. Xin vui lòng cho tôi biết nếu tôi bỏ lỡ bất cứ điều gì.

Q1. Có, trừ khi "enlist = false" được chỉ định trong chuỗi kết nối. Nhóm kết nối tìm thấy một kết nối có thể sử dụng. Một kết nối có thể sử dụng là một kết nối không được ghi danh trong một giao dịch hoặc một kết nối được ghi danh trong cùng một giao dịch.

Quý 2 Kết nối thứ hai là một kết nối độc lập, tham gia vào cùng một giao dịch. Tôi không chắc chắn về sự tương tác của các lệnh trên hai kết nối này, vì chúng chạy trên cùng một cơ sở dữ liệu, nhưng tôi nghĩ lỗi có thể xảy ra nếu các lệnh được phát hành trên cả hai cùng một lúc: lỗi như "Bối cảnh giao dịch được sử dụng bởi phiên khác "

H3 Có, nó được chuyển đến một giao dịch phân tán, do đó, việc tranh thủ nhiều hơn một kết nối, ngay cả với cùng một chuỗi kết nối, khiến nó trở thành một giao dịch phân tán, có thể được xác nhận bằng cách kiểm tra GUID không null tại Transaction.Cản.TransactionInform .DistributionIdentifier. * Cập nhật: Tôi đã đọc ở đâu đó rằng lỗi này được sửa trong SQL Server 2008, do đó MSDTC không được sử dụng khi cùng một chuỗi kết nối được sử dụng cho cả hai kết nối (miễn là cả hai kết nối không mở cùng một lúc). Điều đó cho phép bạn mở một kết nối và đóng nó nhiều lần trong một giao dịch, điều này có thể sử dụng tốt hơn nhóm kết nối bằng cách mở các kết nối càng muộn càng tốt và đóng chúng càng sớm càng tốt.

Q4. Không. Một kết nối được mở khi không có phạm vi giao dịch nào được kích hoạt, sẽ không được tự động liệt kê trong phạm vi giao dịch mới được tạo.

Câu 5. Trừ khi bạn mở một kết nối trong phạm vi giao dịch hoặc tranh thủ một kết nối hiện có trong phạm vi đó, về cơ bản là KHÔNG CÓ GIAO DỊCH. Kết nối của bạn phải được liệt kê tự động hoặc thủ công trong phạm vi giao dịch để các lệnh của bạn tham gia vào giao dịch.

Câu 6. Có, các lệnh trên kết nối không tham gia giao dịch được cam kết như đã ban hành, mặc dù mã xảy ra đã được thực thi trong khối phạm vi giao dịch đã được khôi phục. Nếu kết nối không được liệt kê trong phạm vi giao dịch hiện tại, thì nó không tham gia vào giao dịch, do đó, cam kết hoặc khôi phục giao dịch sẽ không có hiệu lực đối với các lệnh được phát hành trên một kết nối không được liệt kê trong phạm vi giao dịch ... như anh chàng này đã phát hiện ra . Đó là một điều rất khó phát hiện trừ khi bạn hiểu quy trình nhập ngũ tự động: nó chỉ xảy ra khi một kết nối được mở trong phạm vi giao dịch đang hoạt động.

Câu 7. Đúng. Một kết nối hiện có có thể được liệt kê rõ ràng trong phạm vi giao dịch hiện tại bằng cách gọi EnlistTransaction (Transaction.C Hiện tại). Bạn cũng có thể liệt kê một kết nối trên một luồng riêng biệt trong giao dịch bằng cách sử dụng DepitiveTransaction, nhưng giống như trước đây, tôi không chắc hai kết nối liên quan đến cùng một giao dịch với cùng một cơ sở dữ liệu có thể xảy ra như thế nào ... và có thể xảy ra lỗi và tất nhiên kết nối tranh thủ thứ hai làm cho giao dịch leo thang đến một giao dịch phân tán.

Câu 8. Một lỗi có thể được ném. Nếu TransactionScopeOption.Required được sử dụng và kết nối đã được liệt kê trong giao dịch phạm vi giao dịch, thì không có lỗi; thực tế, không có giao dịch mới nào được tạo cho phạm vi và số lượng giao dịch (@@ trancount) không tăng. Tuy nhiên, nếu bạn sử dụng TransactionScopeOption.RequiresNew, thì bạn sẽ nhận được thông báo lỗi hữu ích khi cố gắng tranh thủ kết nối trong giao dịch phạm vi giao dịch mới: "Kết nối hiện đã được liệt kê giao dịch. Kết thúc giao dịch hiện tại và thử lại." Và có, nếu bạn hoàn thành giao dịch, kết nối được ghi danh, bạn có thể tranh thủ kết nối một cách an toàn trong giao dịch mới. Cập nhật: Nếu trước đây bạn đã gọi BeginTransaction trên kết nối, một lỗi hơi khác sẽ xảy ra khi bạn cố gắng ghi danh vào một giao dịch phạm vi giao dịch mới: "Không thể tranh thủ trong giao dịch vì giao dịch cục bộ đang diễn ra trên kết nối. Kết thúc giao dịch cục bộ và thử lại." Mặt khác, bạn có thể gọi BeginTransaction một cách an toàn trên SqlConnection trong khi nó được liệt kê trong một giao dịch phạm vi giao dịch và điều đó thực sự sẽ tăng @@ trancount bởi một, không giống như sử dụng tùy chọn Bắt buộc của phạm vi giao dịch lồng nhau, không gây ra tăng. Thật thú vị, nếu sau đó bạn tiếp tục tạo một phạm vi giao dịch lồng nhau khác với tùy chọn Bắt buộc, bạn sẽ không gặp lỗi,

Câu 9. Đúng. Các lệnh tham gia vào bất kỳ giao dịch nào mà kết nối được liệt kê, bất kể phạm vi giao dịch hoạt động trong mã C # là gì.


11
Sau khi viết câu trả lời cho Q8, tôi nhận ra công cụ này bắt đầu phức tạp như các quy tắc dành cho Magic: The Gathering! Ngoại trừ điều này là tồi tệ hơn, bởi vì tài liệu TransactionScope không giải thích bất kỳ điều này.
Triynko

Đối với Q3, bạn có mở hai kết nối cùng một lúc bằng cùng một chuỗi kết nối không? Nếu vậy, thì đó sẽ là Giao dịch phân tán (ngay cả với SQL Server 2008)
Randy hỗ trợ Monica

2
Không. Tôi chỉnh sửa bài để làm rõ. Tôi hiểu rằng việc có hai kết nối mở cùng một lúc sẽ luôn gây ra giao dịch phân tán, bất kể phiên bản SQL Server. Trước SQL 2008, chỉ mở một kết nối cùng một lúc, với cùng một chuỗi kết nối sẽ gây ra DT, nhưng với SQL 2008, việc mở một kết nối cùng một lúc (không bao giờ có hai kết nối mở cùng một lúc) sẽ không gây ra một kết nối cùng một lúc DT
Triynko

1
Để làm rõ câu trả lời của bạn cho Q2, hai lệnh sẽ chạy tốt nếu chúng được thực hiện tuần tự trên cùng một luồng.
Jared Moore

2
Về vấn đề quảng cáo quý 3 cho các chuỗi kết nối giống hệt nhau trong SQL 2008, đây là trích dẫn MSDN: msdn.microsoft.com/en-us/l Library / ms172070 (v = vs.90) .aspx
pseudocoder

19

Triynko làm việc tốt, tất cả các câu trả lời của bạn trông khá chính xác và đầy đủ với tôi. Một số điều khác tôi muốn chỉ ra:

(1) Nhập ngũ thủ công

Trong mã của bạn ở trên, bạn (chính xác) hiển thị nhập ngũ thủ công như thế này:

using (SqlConnection conn = new SqlConnection(connStr))
{
    conn.Open();
    using (TransactionScope ts = new TransactionScope())
    {
        conn.EnlistTransaction(Transaction.Current);
    }
}

Tuy nhiên, cũng có thể làm như thế này, sử dụng Enlist = false trong chuỗi kết nối.

string connStr = "...; Enlist = false";
using (TransactionScope ts = new TransactionScope())
{
    using (SqlConnection conn1 = new SqlConnection(connStr))
    {
        conn1.Open();
        conn1.EnlistTransaction(Transaction.Current);
    }

    using (SqlConnection conn2 = new SqlConnection(connStr))
    {
        conn2.Open();
        conn2.EnlistTransaction(Transaction.Current);
    }
}

Có một điều cần lưu ý ở đây. Khi Conn2 được mở, mã nhóm kết nối không biết rằng sau đó bạn muốn tranh thủ nó trong cùng một giao dịch với Conn1, điều đó có nghĩa là Conn2 được cung cấp một kết nối nội bộ khác với Conn1. Sau đó, khi Conn2 được liệt kê, hiện có 2 kết nối được liệt kê để giao dịch phải được thăng cấp lên MSDTC. Khuyến mãi này chỉ có thể tránh được bằng cách sử dụng tự động nhập ngũ.

(2) Trước .Net 4.0, tôi đặc biệt khuyên bạn nên đặt "Liên kết giao dịch = Hủy liên kết rõ ràng" trong chuỗi kết nối . Sự cố này được khắc phục trong .Net 4.0, khiến cho Explicit Unbind hoàn toàn không cần thiết.

(3) Lăn của riêng bạn CommittableTransactionvà thiết lập Transaction.Currentđiều đó về cơ bản là giống như những gì TransactionScopelàm. Điều này hiếm khi thực sự hữu ích, chỉ là FYI.

(4) Transaction.Current là chủ đề tĩnh. Điều này có nghĩa Transaction.Currentlà chỉ được đặt trên luồng đã tạo TransactionScope. Vì vậy, nhiều luồng thực thi giống nhau TransactionScope(có thể sử dụng Task) là không thể.


Tôi vừa thử nghiệm kịch bản này, và nó dường như hoạt động như bạn mô tả. Ngoài ra, ngay cả khi bạn sử dụng chế độ nhập ngũ tự động, nếu bạn gọi "SqlConnection.ClearAllPools ()" trước khi mở kết nối thứ hai, thì nó sẽ được chuyển sang giao dịch phân tán.
Triynko

Nếu điều này là đúng, thì chỉ có thể có một kết nối "thực" duy nhất liên quan đến một giao dịch. Khả năng mở, đóng và mở lại một kết nối được liệt kê trong giao dịch TransactionScope mà không chuyển sang giao dịch phân tán sau đó thực sự là một ảo ảnh được tạo bởi nhóm kết nối , thông thường sẽ mở kết nối bị xử lý và trả lại kết nối chính xác đó nếu lại -opened cho nhập ngũ tự động.
Triynko

Vì vậy, điều bạn thực sự nói là nếu bạn bỏ qua quy trình nhập ngũ tự động, thì khi bạn mở lại một kết nối mới bên trong một giao dịch phạm vi giao dịch (TST), thay vì nhóm kết nối lấy đúng kết nối (ban đầu là kết nối tranh thủ trong TST), nó hoàn toàn nắm bắt được một kết nối hoàn toàn mới, khi nhập ngũ theo cách thủ công, khiến TST leo thang.
Triynko

Dù sao, đó chính xác là những gì tôi đã gợi ý trong câu trả lời của mình cho Q1 khi tôi đề cập rằng nó đã được liệt kê trừ khi "Enlist = false" được chỉ định trong chuỗi kết nối, sau đó nói về cách nhóm tìm thấy kết nối phù hợp.
Triynko

Theo như đa luồng, nếu bạn truy cập liên kết trong câu trả lời của tôi cho Q2, bạn sẽ thấy rằng trong khi Transaction.C Hiện tại là duy nhất cho mỗi luồng, bạn có thể dễ dàng có được tham chiếu trong một luồng và chuyển nó sang luồng khác; tuy nhiên, việc truy cập TST từ hai luồng khác nhau dẫn đến một lỗi rất cụ thể "Bối cảnh giao dịch được sử dụng bởi một phiên khác". Để đa luồng một TST, bạn phải tạo DepantTransaction, nhưng tại thời điểm đó, nó phải là một giao dịch phân tán, vì bạn cần một kết nối độc lập thứ hai để thực sự chạy các lệnh đồng thời và MSDTC để phối hợp cả hai.
Triynko

1

Một tình huống kỳ lạ khác mà chúng tôi đã thấy là nếu bạn xây dựng một EntityConnectionStringBuildernó sẽ bị mắc kẹt TransactionScope.Currentvà (chúng tôi nghĩ) tranh thủ trong giao dịch. Chúng tôi đã quan sát này trong trình gỡ lỗi, nơi TransactionScope.Current's current.TransactionInformation.internalTransactionchương trình enlistmentCount == 1trước khi xây dựng, và enlistmentCount == 2sau đó.

Để tránh điều này, xây dựng nó bên trong

using (new TransactionScope(TransactionScopeOption.Suppress))

và có thể nằm ngoài phạm vi hoạt động của bạn (chúng tôi đã xây dựng nó mỗi khi chúng tôi cần kết nối).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.