Hangi koşullar altında bir SqlConnection otomatik olarak bir ortam TransactionScope İşlemine kaydolur?


201

Bir SqlConnection'ın bir işleme "dahil edilmesi" ne anlama gelir? Sadece bağlantıda yürüttüğüm komutların işleme katılacağı anlamına mı geliyor?

Öyleyse, SqlConnection hangi koşullarda otomatik olarak bir ortam TransactionScope İşlemine kaydolur ?

Kod yorumlarındaki sorulara bakın. Her sorunun cevabına ilişkin tahminim parantez içindeki her soruyu takip ediyor.

Senaryo 1: Bir işlem kapsamının İÇİNDEKİ bağlantıları açma

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?)
}

Senaryo 2: DIŞINDA açılan bir işlem kapsamının İÇİNDEKİ bağlantıları kullanma

//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?)
}

Yanıtlar:


188

Bu soruyu sorduğumuzdan beri bazı testler yaptım ve cevapları kendi başıma olmasa da çoğunu kendi başıma buldum, çünkü kimse cevaplamadı. Bir şey kaçırırsam lütfen bize bildirin.

S1. Bağlantı dizesinde "enlist = false" belirtilmedikçe evet. Bağlantı havuzu kullanılabilir bir bağlantı bulur. Kullanılabilir bir bağlantı, bir işleme kayıtlı olmayan veya aynı işleme kayıtlı olan bağlantıdır.

S2. İkinci bağlantı, aynı işleme katılan bağımsız bir bağlantıdır. Aynı veritabanında çalıştıklarından, bu iki bağlantıdaki komutların etkileşimi hakkında emin değilim, ancak komutların her ikisinde de aynı anda yayınlanması durumunda hataların oluşabileceğini düşünüyorum: " başka bir oturum "

Q3. Evet, dağıtılmış bir işleme yükseltilir, bu nedenle aynı bağlantı dizesiyle bile birden fazla bağlantıyı içerir, bu işlem Transaction.Cull.TransactionInformation öğesinde boş olmayan bir GUID denetlenerek onaylanabilen dağıtılmış bir işlem haline gelir. .DistributedIdentifier. * Güncelleme: Bu, SQL Server 2008'de düzeltildiği bir yerde okudum, böylece her iki bağlantı için aynı bağlantı dizesi kullanıldığında (her iki bağlantı aynı anda açık olmadığı sürece) MSDTC kullanılmaz. Bu, bir bağlantıda bir bağlantı açmanıza ve birden çok kez kapatmanıza olanak tanır, bu da bağlantıları mümkün olduğunca geç açıp mümkün olan en kısa sürede kapatarak bağlantı havuzunu daha iyi kullanabilir.

S4. Hayır. Hiçbir işlem kapsamı aktif olmadığında açılan bir bağlantı otomatik olarak yeni oluşturulan bir işlem kapsamına alınmaz.

Q5. Hayır. İşlem kapsamında bir bağlantı açmazsanız veya kapsamda var olan bir bağlantıyı kaydetmezseniz, temelde İŞLEM YOKTUR. Komutlarınızın işleme katılması için bağlantınızın işlem kapsamına otomatik olarak veya el ile kaydedilmesi gerekir.

S6. Evet, kod geri alınan bir işlem kapsamı bloğunda yürütülmüş olsa bile, bir işleme katılmayan bir bağlantıdaki komutlar yayınlandığı gibi işlenir. Bağlantı cari işlem kapsamı içinde kayıtlı değilse, o kadar yani ... işlemekle ya da işlem kapsamı listesinde yok bir bağlantıda verilen komutlara üzerinde hiçbir etkisi olmayacaktır hareketi geri, işlem katılan değil bu adam öğrendim . Otomatik kayıt işlemini anlamadığınız sürece fark edilmesi çok zor bir işlemdir: yalnızca etkin bir işlem kapsamı içinde bir bağlantı açıldığında gerçekleşir.

Q7. Evet. Varolan bir bağlantı, EnlistTransaction (Transaction.Current) çağrılarak geçerli işlem kapsamına açıkça kaydedilebilir. Ayrıca, bir DependentTransaction kullanarak işlemde ayrı bir iş parçacığına bağlantı kaydedebilirsiniz, ancak daha önce olduğu gibi, aynı veritabanında aynı işlemde yer alan iki bağlantının nasıl etkileşime girebileceğinden emin değilim ve hatalar oluşabilir ve elbette ki ikinci kayıtlı bağlantı, işlemin dağıtılmış bir işleme yükselmesine neden olur.

S8. Bir hata atılabilir. TransactionScopeOption.Required kullanıldıysa ve bağlantı zaten bir işlem kapsamı işlemine kayıtlıysa, hata yoktur; aslında, kapsam için yeni bir işlem oluşturulmaz ve işlem sayısı (@@ trancount) artmaz. Ancak, TransactionScopeOption.RequiresNew kullanıyorsanız, bağlantıyı yeni işlem kapsamı işlemine kaydettirmeye çalıştığınızda faydalı bir hata iletisi alırsınız: "Bağlantı şu anda işlem kayıtlı. Geçerli işlemi tamamlayın ve yeniden deneyin." Ve evet, bağlantının kayıtlı olduğu işlemi tamamlarsanız, bağlantıyı güvenli bir şekilde yeni bir işleme kaydedebilirsiniz. Güncelleştirme: Bağlantıda daha önce BeginTransaction öğesini çağırdıysanız, yeni bir işlem kapsamı işlemine kaydolmaya çalıştığınızda biraz farklı bir hata atılır: "Bağlantıda yerel bir işlem devam ettiği için işleme kaydedilemiyor. Yerel işlemi bitirin ve yeniden deneyin." Öte yandan, bir işlem kapsamı işlemine kayıtlıyken SqlConnection'da BeginTransaction'ı güvenle çağırabilirsiniz ve bu, iç içe bir işlem kapsamının Gerekli seçeneğini kullanmanın aksine, @@ sayımını birer birer artıracaktır. artırmak. İlginç bir şekilde, daha sonra Gerekli seçeneğiyle başka bir iç içe işlem kapsamı oluşturmaya devam ederseniz, bir hata almazsınız,

S9. Evet. Komutlar, etkin işlem kapsamının C # kodunda ne olduğuna bakılmaksızın, bağlantının kayıtlı olduğu tüm işlemlere katılır.


11
S8'in cevabını yazdıktan sonra, bu şeyin Magic: The Gathering için kurallar kadar karmaşık görünmeye başladığını anlıyorum. TransactionScope belgeleri bunların hiçbirini açıklamadığı için bu daha kötüdür.
Triynko

Q3 için, aynı bağlantı dizesini kullanarak aynı anda iki bağlantı mı açıyorsunuz? Öyleyse, bu bir Dağıtılmış İşlem olacaktır (SQL Server 2008 ile bile)
Randy,

2
Hayır. Açıklığa kavuşturmak için gönderiyi düzenliyorum. Anladığım kadarıyla iki bağlantı aynı anda açık olması, SQL Server sürümüne bakılmaksızın her zaman dağıtılmış bir işleme neden olacaktır. SQL 2008'den önce, aynı bağlantı dizesiyle bir seferde yalnızca bir bağlantı açmak yine de DT'ye neden olur, ancak SQL 2008 ile aynı bağlantı dizesiyle aynı anda bir bağlantıyı açmak (asla aynı anda iki kez açılmamak) neden olmaz. DT
Triynko

1
Q2'ye verdiğiniz cevabı açıklığa kavuşturmak için, iki komut aynı iş parçacığında sırayla yapılırsa düzgün çalışmalıdır.
Jared Moore

2
SQL 2008'deki aynı bağlantı dizeleri için üçüncü çeyrek tanıtım sorunu için MSDN alıntısı: msdn.microsoft.com/en-us/library/ms172070(v=vs.90).aspx
pseudocoder

19

İyi iş Triynko, cevaplarınızın hepsi oldukça doğru ve eksiksiz görünüyor. Belirtmek istediğim diğer bazı şeyler:

(1) Manuel kayıt

Yukarıdaki kodunuzda, (manuel olarak) şu şekilde manuel kaydı göstermeniz gerekir:

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

Ancak, bağlantı dizesinde Enlist = false ifadesini kullanarak bunu yapmak da mümkündür.

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);
    }
}

Burada dikkat edilmesi gereken başka bir şey daha var. Conn2 açıldığında, bağlantı havuzu kodu daha sonra onu conn1 ile aynı işleme kaydetmek istediğinizi bilmez; bu, conn2'ye conn1'den farklı bir dahili bağlantı verildiği anlamına gelir. Daha sonra conn2 kaydedildiğinde, şimdi 2 bağlantı vardır, bu nedenle işlemin MSDTC'ye yükseltilmesi gerekir. Bu tanıtım yalnızca otomatik kayıt kullanılarak önlenebilir.

(2) .Net 4.0'dan önce , bağlantı dizesinde "Transaction Binding = Explicit Unbind" ayarını öneririm . Bu sorun .Net 4.0'da düzeltildi ve Açık Bağlama'yı tamamen gereksiz kıldı.

(3) Kendinizi yuvarlamak CommittableTransactionve bunu ayarlamak Transaction.Current, aslında ne yaptığınızla aynı şeydir TransactionScope. Bu nadiren faydalıdır, sadece FYI.

(4) Transaction.Current iplik statiktir. Bu Transaction.Current, yalnızca TransactionScope. Bu nedenle, aynı işlemi TransactionScope(muhtemelen kullanarak Task) yürüten birden fazla iş parçacığı mümkün değildir.


Bu senaryoyu yeni test ettim ve tanımladığınız gibi çalışıyor. Ayrıca, otomatik kayıt kullansanız bile, ikinci bağlantıyı açmadan önce "SqlConnection.ClearAllPools ()" çağırırsanız, dağıtılmış bir işleme yükseltilir.
Triynko

Bu doğruysa, o zaman sadece bir işleme dahil olan tek bir "gerçek" bağlantı olabilir. Bir TransactionScope işleminde listelenen bir bağlantıyı dağıtılmış bir işleme geçmeden açma, kapatma ve yeniden açma yeteneği, bağlantı havuzu tarafından oluşturulan ve normalde atılan bağlantıyı açık bırakacak ve aynı bağlantıyı yeniden döndürecekse gerçekten bir yanılsamadır. otomatik kayıt için açıldı.
Triynko

Gerçekten söylediğiniz şey, otomatik kayıt işlemine son verdiğinizde, doğru bağlantıyı (başlangıçta olanı) kaplayan bağlantı havuzu yerine bir işlem kapsamı işleminin (TST) içinde yeni bir bağlantıyı yeniden açtığınızda Tamamen yeni bir bağlantı yakalar, bu da manuel olarak kaydedildiğinde TST'nin yükselmesine neden olur.
Triynko

Her neyse, bağlantı dizesinde "Enlist = false" belirtilmemişse, havuzun uygun bir bağlantıyı nasıl bulduğundan bahsettiğimde, Q1'e cevabımda tam olarak bahsettiğim şey tam olarak buydu.
Triynko

Çok iş parçacığının gittiği sürece, Q2'ye cevabımdaki bağlantıyı ziyaret ederseniz, Transaction.Current'in her bir iş parçacığına özgü olmasına rağmen, referansı bir iş parçacığında kolayca edinebilir ve başka bir iş parçacığına iletebilirsiniz; ancak, iki farklı iş parçacığından bir TST'ye erişmek çok özel bir hatayla sonuçlanır "Başka bir oturum tarafından kullanılan işlem içeriği". Bir TST'yi çok iş parçacıklı hale getirmek için bir DependantTransaction oluşturmalısınız, ancak bu noktada dağıtılmış bir işlem olmalıdır, çünkü ikisini koordine etmek için aynı anda komutları ve MSDTC'yi çalıştırmak için ikinci bir bağımsız bağlantıya ihtiyacınız vardır.
Triynko

1

Gördüğümüz diğer bir tuhaf durum ise, eğer bir inşa ederseniz, EntityConnectionStringBuilderbununla uğraşacak TransactionScope.Currentve (biz düşünüyoruz) işleme girecektir. Biz ayıklayıcıya, bu gözlenen ettik TransactionScope.Current's current.TransactionInformation.internalTransactiongösterileri enlistmentCount == 1inşa ve önce enlistmentCount == 2sonra.

Bundan kaçınmak için içine inşa edin

using (new TransactionScope(TransactionScopeOption.Suppress))

ve muhtemelen operasyonunuzun kapsamı dışında (her bağlantıya ihtiyaç duyduğumuzda inşa ediyorduk).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.