Dalam keadaan apa SqlConnection secara otomatis terdaftar dalam TransactionScope ambient Transaction?

201

Apa artinya sebuah SqlConnection untuk "didaftar" dalam transaksi? Apakah itu hanya berarti bahwa perintah yang saya jalankan pada koneksi akan berpartisipasi dalam transaksi?

Jika demikian, dalam keadaan apa SqlConnection secara otomatis terdaftar dalam TransactionScope ambient Transaction?

Lihat pertanyaan dalam komentar kode. Tebakan saya untuk setiap jawaban pertanyaan mengikuti setiap pertanyaan dalam tanda kurung.

Skenario 1: Membuka koneksi DI DALAM lingkup transaksi

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

Skenario 2: Menggunakan koneksi DALAM lingkup transaksi yang dibuka DI LUAR itu

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

Jawaban:

188

Saya telah melakukan beberapa tes sejak mengajukan pertanyaan ini dan menemukan sebagian besar jika tidak semua jawaban saya sendiri, karena tidak ada orang lain yang menjawab. Tolong beri tahu saya jika saya melewatkan sesuatu.

Q1. Ya, kecuali "enlist = false" ditentukan dalam string koneksi. Kumpulan koneksi menemukan koneksi yang dapat digunakan. Koneksi yang dapat digunakan adalah koneksi yang tidak terdaftar dalam transaksi atau koneksi yang terdaftar dalam transaksi yang sama.

Q2. Koneksi kedua adalah koneksi independen, yang berpartisipasi dalam transaksi yang sama. Saya tidak yakin tentang interaksi perintah pada dua koneksi ini, karena mereka berjalan melawan database yang sama, tetapi saya pikir kesalahan dapat terjadi jika perintah dikeluarkan pada keduanya pada saat yang sama: kesalahan seperti "Konteks transaksi digunakan oleh sesi lain "

Q3. Ya, itu akan meningkat menjadi transaksi terdistribusi, jadi mendaftar lebih dari satu koneksi, bahkan dengan string koneksi yang sama, menyebabkannya menjadi transaksi terdistribusi, yang dapat dikonfirmasi dengan memeriksa untuk GUID non-nol di Transaction.Current.TransactionInformation .DistributedIdentifier. * Pembaruan: Saya membaca di suatu tempat bahwa ini diperbaiki di SQL Server 2008, sehingga MSDTC tidak digunakan ketika string koneksi yang sama digunakan untuk kedua koneksi (selama kedua koneksi tidak terbuka pada saat yang sama). Itu memungkinkan Anda untuk membuka koneksi dan menutupnya berkali-kali dalam suatu transaksi, yang dapat memanfaatkan kolam koneksi dengan lebih baik dengan membuka koneksi selambat mungkin dan menutupnya sesegera mungkin.

Q4. Tidak. Koneksi dibuka ketika tidak ada ruang lingkup transaksi yang aktif, tidak akan secara otomatis terdaftar dalam ruang lingkup transaksi yang baru dibuat.

Q5. Tidak. Kecuali Anda membuka koneksi dalam ruang lingkup transaksi, atau meminta koneksi yang ada dalam ruang lingkup, pada dasarnya TIDAK ADA TRANSAKSI. Koneksi Anda harus secara otomatis atau manual terdaftar dalam ruang lingkup transaksi agar perintah Anda dapat berpartisipasi dalam transaksi.

Q6. Ya, perintah pada koneksi yang tidak berpartisipasi dalam transaksi dilakukan seperti yang dikeluarkan, meskipun kode kebetulan telah dieksekusi di blok lingkup transaksi yang dibatalkan. Jika koneksi tidak terdaftar dalam lingkup transaksi saat ini, itu tidak berpartisipasi dalam transaksi, jadi melakukan atau memutar kembali transaksi tidak akan berpengaruh pada perintah yang dikeluarkan pada koneksi yang tidak terdaftar dalam lingkup transaksi ... karena orang ini mengetahui . Itu sangat sulit dikenali kecuali Anda memahami proses pendaftaran otomatis: itu terjadi hanya ketika koneksi dibuka di dalam ruang lingkup transaksi aktif.

Q7. Iya. Koneksi yang ada dapat secara eksplisit terdaftar dalam ruang lingkup transaksi saat ini dengan memanggil EnlistTransaction (Transaction.Current). Anda juga dapat meminta koneksi pada utas terpisah dalam transaksi dengan menggunakan DependentTransaction, tetapi seperti sebelumnya, saya tidak yakin bagaimana dua koneksi yang terlibat dalam transaksi yang sama terhadap basis data yang sama dapat berinteraksi ... dan kesalahan dapat terjadi, dan tentu saja koneksi tamtama kedua menyebabkan transaksi meningkat ke transaksi terdistribusi.

Q8. Kesalahan dapat terjadi. Jika TransactionScopeOption.Required digunakan, dan koneksi sudah terdaftar dalam transaksi lingkup transaksi, maka tidak ada kesalahan; pada kenyataannya, tidak ada transaksi baru yang dibuat untuk ruang lingkup, dan jumlah transaksi (@@ trancount) tidak meningkat. Namun, jika Anda menggunakan TransactionScopeOption.RequiresNew, maka Anda mendapatkan pesan kesalahan yang membantu setelah mencoba untuk mendaftar koneksi dalam transaksi lingkup transaksi baru: "Koneksi saat ini memiliki transaksi terdaftar. Selesaikan transaksi saat ini dan coba lagi." Dan ya, jika Anda menyelesaikan transaksi koneksi terdaftar, Anda dapat mendaftar koneksi dengan aman dalam transaksi baru. Pembaruan: Jika sebelumnya Anda menyebut BeginTransaction pada koneksi, kesalahan yang sedikit berbeda terjadi ketika Anda mencoba mendaftar dalam transaksi lingkup transaksi baru: "Tidak dapat mendaftar dalam transaksi karena transaksi lokal sedang berlangsung pada koneksi. Selesaikan transaksi lokal dan mencoba kembali." Di sisi lain, Anda dapat dengan aman memanggil BeginTransaction di SqlConnection ketika terdaftar dalam transaksi lingkup transaksi, dan itu akan benar-benar meningkatkan @@ trancount per satu, tidak seperti menggunakan opsi Diperlukan dari ruang lingkup transaksi bersarang, yang tidak menyebabkannya meningkat. Menariknya, jika Anda kemudian membuat ruang lingkup transaksi bersarang dengan opsi Diperlukan, Anda tidak akan mendapatkan kesalahan,

Q9. Iya. Perintah berpartisipasi dalam transaksi apa pun yang dicantumkan koneksi, terlepas dari apa lingkup transaksi aktif dalam kode C #.

Triynko
sumber
11
Setelah menulis jawaban untuk Q8, saya menyadari hal ini mulai terlihat rumit seperti aturan untuk Magic: The Gathering! Kecuali ini lebih buruk, karena dokumentasi TransactionScope tidak menjelaskan semua ini.
Triynko
Untuk Q3, apakah Anda membuka dua koneksi sekaligus menggunakan string koneksi yang sama? Jika demikian, maka itu akan menjadi Transaksi Terdistribusi (bahkan dengan SQL Server 2008)
Randy mendukung Monica
2
Tidak. Saya mengedit posting untuk mengklarifikasi. Pemahaman saya adalah bahwa dua koneksi terbuka pada saat yang sama akan selalu menyebabkan transaksi terdistribusi, terlepas dari versi SQL Server. Sebelum SQL 2008, hanya membuka satu koneksi pada satu waktu, dengan string koneksi yang sama masih akan menyebabkan DT, tetapi dengan SQL 2008, membuka satu koneksi pada satu waktu (tidak pernah memiliki dua koneksi sekaligus) dengan string koneksi yang sama tidak akan menyebabkan DT
Triynko
1
Untuk memperjelas jawaban Anda untuk Q2, kedua perintah harus berjalan dengan baik jika dilakukan secara berurutan di utas yang sama.
Jared Moore
2
Pada masalah promosi Q3 untuk string koneksi identik dalam SQL 2008, berikut adalah kutipan MSDN: msdn.microsoft.com/en-us/library/ms172070(v=vs.90).aspx
pseudocoder
19

Kerja bagus Triynko, jawaban Anda semua terlihat cukup akurat dan lengkap untuk saya. Beberapa hal lain yang ingin saya tunjukkan:

(1) Pendaftaran manual

Dalam kode Anda di atas, Anda (dengan benar) menunjukkan pendaftaran manual seperti ini:

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

Namun, juga dimungkinkan untuk melakukannya seperti ini, menggunakan Enlist = false di string koneksi.

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

Ada hal lain yang perlu diperhatikan di sini. Ketika conn2 dibuka, kode kumpulan koneksi tidak tahu bahwa Anda ingin mendaftarkannya nanti dalam transaksi yang sama dengan conn1, yang berarti bahwa conn2 diberikan koneksi internal yang berbeda dari conn1. Kemudian ketika conn2 terdaftar, sekarang ada 2 koneksi terdaftar sehingga transaksi harus dipromosikan ke MSDTC. Promosi ini hanya dapat dihindari dengan menggunakan pendaftaran otomatis.

(2) Sebelum .Net 4.0, saya sangat merekomendasikan pengaturan "Binding Transaksi = Eksplisit Terikat" dalam string koneksi . Masalah ini diperbaiki di .Net 4.0, membuat Explicit Unbind benar-benar tidak perlu.

(3) Menggulung CommittableTransactionpengaturan dan pengaturan Anda sendiri Transaction.Currentpada dasarnya sama dengan apa yang TransactionScopedilakukan. Ini jarang benar-benar bermanfaat, hanya FYI.

(4) Transaction.Current bersifat statis. Ini berarti bahwa Transaction.Currenthanya diatur pada utas yang menciptakan TransactionScope. Jadi beberapa utas yang menjalankan hal yang sama TransactionScope(mungkin menggunakan Task) tidak mungkin.

Jared Moore
sumber
Saya baru saja menguji skenario ini, dan sepertinya berfungsi seperti yang Anda gambarkan. Selain itu, bahkan jika Anda menggunakan pendaftaran otomatis, jika Anda memanggil "SqlConnection.ClearAllPools ()" sebelum membuka koneksi kedua, maka itu akan meningkat menjadi transaksi terdistribusi.
Triynko
Jika ini benar, maka hanya akan ada satu koneksi "nyata" yang terlibat dalam suatu transaksi. Kemampuan untuk membuka, menutup, dan membuka kembali koneksi yang terdaftar dalam transaksi TransactionScope tanpa meningkatkan ke transaksi terdistribusi kemudian benar-benar ilusi yang dibuat oleh kumpulan koneksi , yang biasanya akan membiarkan koneksi yang dibuang terbuka, dan mengembalikan koneksi yang sama persis jika kembali -Dibuka untuk pendaftaran otomatis.
Triynko
Jadi apa yang sebenarnya Anda katakan adalah bahwa jika Anda menghindari proses pendaftaran otomatis, maka ketika Anda membuka kembali koneksi baru di dalam transaksi lingkup transaksi (TST), bukannya kumpulan koneksi mengambil koneksi yang benar (yang awalnya terdaftar di TST), itu cukup tepat mengambil koneksi yang sama sekali baru, yang ketika mendaftar secara manual, menyebabkan TST meningkat.
Triynko
Ngomong-ngomong, itulah yang saya sebutkan di jawaban saya untuk Q1 ketika saya menyebutkan bahwa itu terdaftar kecuali "Enlist = false" ditentukan dalam string koneksi, kemudian berbicara tentang bagaimana pool menemukan koneksi yang cocok.
Triynko
Sejauh multi-threading, jika Anda mengunjungi tautan dalam jawaban saya untuk Q2, Anda akan melihat bahwa sementara Transaction.Current unik untuk setiap utas, Anda dapat dengan mudah memperoleh referensi dalam satu utas dan meneruskannya ke utas lain; Namun, mengakses TST dari dua utas berbeda menghasilkan kesalahan "konteks transaksi yang digunakan oleh sesi lain" yang sangat spesifik. Untuk membuat TST multi-utas, Anda harus membuat DependantTransaction, tetapi pada saat itu harus berupa transaksi terdistribusi, karena Anda memerlukan koneksi independen kedua untuk benar-benar menjalankan perintah simultan dan MSDTC untuk mengoordinasikan keduanya.
Triynko
1

Satu situasi aneh lain yang telah kita lihat adalah bahwa jika Anda membangunnya EntityConnectionStringBuilderakan berantakan dengan TransactionScope.Currentdan (kami pikir) mendaftar dalam transaksi. Kami telah mengamati ini di debugger, di mana TransactionScope.Current's current.TransactionInformation.internalTransactionmenunjukkan enlistmentCount == 1sebelum membangun, dan enlistmentCount == 2sesudahnya.

Untuk menghindari ini, buatlah di dalam

using (new TransactionScope(TransactionScopeOption.Suppress))

dan mungkin di luar ruang lingkup operasi Anda (kami membangunnya setiap kali kami membutuhkan koneksi).

Todd
sumber