TL; DR / Ringkasan Eksekutif: Mengenai bagian Pertanyaan ini:
Saya tidak melihat dalam kasus apa kontrol dapat diteruskan ke dalam CATCHdengan transaksi yang dapat dilakukan ketika XACT_ABORTdiatur keON .
Saya telah melakukan cukup banyak pengujian pada ini sekarang dan saya tidak dapat menemukan kasus di mana XACT_STATE()kembali 1dalam CATCHblok kapan @@TRANCOUNT > 0 dan properti sesi XACT_ABORTadalah ON. Dan pada kenyataannya, menurut halaman MSDN saat ini untuk SET XACT_ABORT :
Ketika SET XACT_ABORT HIDUP, jika pernyataan Transact-SQL memunculkan kesalahan run-time, seluruh transaksi diakhiri dan dibatalkan.
Pernyataan itu tampaknya sesuai dengan spekulasi Anda dan temuan saya.
Artikel MSDN tentang SET XACT_ABORTmemiliki contoh ketika beberapa pernyataan di dalam transaksi dieksekusi dengan sukses dan beberapa gagal ketika XACT_ABORTdiatur keOFF
Benar, tetapi pernyataan dalam contoh itu tidak berada dalam TRYblok. Pernyataan-pernyataan yang sama dalam TRYblok masih akan mencegah eksekusi untuk setiap pernyataan setelah satu yang menyebabkan kesalahan, tetapi dengan asumsi bahwa XACT_ABORTadalah OFF, ketika kontrol akan diteruskan ke CATCHblok Transaksi masih berlaku fisik dalam semua perubahan sebelum itu terjadi tanpa kesalahan dan dapat dilakukan, jika itu adalah keinginan, atau mereka dapat dibatalkan. Di sisi lain, jika XACT_ABORTada ONmaka perubahan sebelumnya secara otomatis dibatalkan, dan kemudian Anda diberikan pilihan untuk: a) menerbitkanROLLBACKyang kebanyakan hanya penerimaan situasi sejak Transaksi itu sudah digulung kembali dikurangi ulang @@TRANCOUNTke 0, atau b) mendapatkan error. Tidak banyak pilihan, kan?
Satu detail penting yang mungkin untuk teka-teki ini yang tidak jelas dalam dokumentasi untuk SET XACT_ABORTadalah bahwa properti sesi ini, dan contoh kode itu, telah ada sejak SQL Server 2000 (dokumentasi hampir identik di antara versi), mendahului TRY...CATCHkonstruk yang sebelumnya diperkenalkan di SQL Server 2005. melihat dokumentasi yang lagi dan melihat contoh ( tanpa yang TRY...CATCH), menggunakan XACT_ABORT ONpenyebab suatu segera roll-back Transaksi: tidak ada negara Transaksi "uncommittable" (mohon perhatikan bahwa tidak ada disebutkan di semua status Transaksi "tidak dapat dikomit" dalam SET XACT_ABORTdokumentasi itu).
Saya pikir masuk akal untuk menyimpulkan bahwa:
- pengenalan
TRY...CATCHkonstruk dalam SQL Server 2005 menciptakan kebutuhan untuk status Transaksi baru (yaitu "tidak dapat dikomit") dan XACT_STATE()fungsi untuk mendapatkan informasi itu.
- memeriksa
XACT_STATE()di CATCHblok benar-benar hanya masuk akal jika kedua berikut ini benar:
XACT_ABORTadalah OFF(yang lain XACT_STATE()harus selalu kembali -1dan @@TRANCOUNTakan menjadi semua yang Anda butuhkan)
- Anda memiliki logika di
CATCHblok, atau di suatu tempat di atas rantai jika panggilan bersarang, yang membuat perubahan ( COMMITatau bahkan pernyataan DML, DDL, dll) daripada melakukan ROLLBACK. (ini adalah kasus penggunaan yang sangat atipikal) ** silakan lihat catatan di bagian bawah, di bagian UPDATE 3, mengenai rekomendasi yang tidak resmi oleh Microsoft untuk selalu memeriksa XACT_STATE()bukan @@TRANCOUNT, dan mengapa pengujian menunjukkan bahwa alasan mereka tidak berjalan.
- pengantar
TRY...CATCHkonstruk dalam SQL Server 2005, untuk sebagian besar, mengacaukan XACT_ABORT ONproperti sesi karena menyediakan tingkat kontrol yang lebih besar atas Transaksi (Anda setidaknya memiliki pilihan untuk COMMIT, asalkan XACT_STATE()tidak kembali -1).
Cara lain untuk melihat ini adalah, sebelum SQL Server 2005 , XACT_ABORT ONmenyediakan cara yang mudah dan dapat diandalkan untuk berhenti memproses ketika kesalahan terjadi, dibandingkan dengan memeriksa @@ERRORsetelah setiap pernyataan.
- Dokumentasi contoh kode untuk
XACT_STATE()adalah salah, atau paling menyesatkan, dalam hal ini menunjukkan memeriksa XACT_STATE() = 1saat XACT_ABORTini ON.
Bagian yang panjang ;-)
Ya, kode contoh pada MSDN agak membingungkan (lihat juga: @@ TRANCOUNT (Kembalikan) vs XACT_STATE ) ;-). Dan, saya merasa itu menyesatkan karena baik menunjukkan sesuatu yang tidak masuk akal (untuk alasan bahwa Anda bertanya tentang: dapat Anda bahkan memiliki "committable" transaksi di CATCHblok saat XACT_ABORTini ON), atau bahkan jika mungkin, masih berfokus pada kemungkinan teknis yang sedikit orang akan inginkan atau butuhkan, dan mengabaikan alasan seseorang lebih mungkin membutuhkannya.
Jika ada kesalahan yang cukup parah di dalam blok TRY, kontrol akan masuk ke CATCH. Jadi, jika saya berada di dalam CATCH, saya tahu bahwa transaksi memiliki masalah dan satu-satunya hal yang masuk akal untuk dilakukan dalam kasus ini adalah mengembalikannya, bukan?
Saya pikir itu akan membantu jika kita memastikan bahwa kita berada di halaman yang sama mengenai apa yang dimaksud dengan kata-kata dan konsep tertentu:
"kesalahan cukup parah": Hanya untuk memperjelas, COBA ... CATCH akan menjebak sebagian besar kesalahan. Daftar apa yang tidak akan ditangkap tercantum pada halaman MSDN yang tertaut, di bawah bagian "Kesalahan Tidak Terpengaruh oleh TRY ... CATCH Construct".
"Jika saya berada di dalam CATCH, saya tahu bahwa transaksi memiliki masalah" (em phas ditambahkan): Jika dengan "transaksi" yang Anda maksudkan adalah unit kerja logis yang ditentukan oleh Anda dengan mengelompokkan laporan ke dalam transaksi eksplisit, maka kemungkinan besar ya. Saya pikir sebagian besar dari kita orang-orang DB akan cenderung setuju bahwa rolling-back adalah "satu-satunya hal yang masuk akal untuk dilakukan" karena kita cenderung memiliki pandangan yang sama tentang bagaimana dan mengapa kita menggunakan transaksi eksplisit dan membayangkan langkah-langkah apa yang harus dilakukan dalam unit atom. pekerjaan.
Tetapi, jika yang Anda maksud adalah unit kerja aktual yang dikelompokkan ke dalam transaksi eksplisit, maka tidak, Anda tidak tahu bahwa transaksi itu sendiri memiliki masalah. Anda hanya tahu bahwa suatu pernyataan mengeksekusi dalam transaksi didefinisikan secara eksplisit telah mengangkat kesalahan. Tapi itu mungkin bukan pernyataan DML atau DDL. Dan bahkan jika itu adalah pernyataan DML, Transaksi itu sendiri mungkin masih dapat dilakukan.
Mengingat dua poin yang dibuat di atas, kita mungkin harus membedakan antara transaksi yang Anda "tidak bisa" lakukan, dan yang Anda "tidak ingin" lakukan.
Ketika XACT_STATE()mengembalikan a 1, itu berarti bahwa Transaksi "layak", bahwa Anda memiliki pilihan antara COMMITatau ROLLBACK. Anda mungkin tidak ingin mengkomitnya, tetapi jika untuk beberapa alasan yang sulit dicapai bahkan dengan contoh-karena-Anda ingin, setidaknya Anda bisa karena beberapa bagian dari Transaksi itu selesai dengan sukses.
Tetapi ketika XACT_STATE()mengembalikan a -1, maka Anda benar-benar perlu ROLLBACKkarena beberapa bagian dari Transaksi mengalami keadaan yang buruk. Sekarang, saya setuju bahwa jika kontrol telah diteruskan ke blok CATCH, maka cukup masuk akal untuk memeriksa saja @@TRANCOUNT, karena bahkan jika Anda dapat melakukan Transaksi, mengapa Anda ingin melakukannya?
Tetapi jika Anda perhatikan di bagian atas contoh, pengaturan XACT_ABORT ONperubahan sedikit. Anda dapat memiliki kesalahan biasa, setelah melakukan BEGIN TRANitu akan melewati kontrol ke blok CATCH saat XACT_ABORTini OFFdan XACT_STATE () akan kembali 1. TETAPI, jika XACT_ABORT adalah ON, maka Transaksi itu "dibatalkan" (yaitu tidak valid) untuk kesalahan apa pun dan kemudian XACT_STATE()akan kembali -1. Dalam hal ini, tampaknya tidak berguna untuk memeriksa XACT_STATE()di dalamCATCH blok seperti yang selalu tampaknya mengembalikan -1saat XACT_ABORTini ON.
Jadi apa itu XACT_STATE() ? Beberapa petunjuk adalah:
Halaman MSDN untuk TRY...CATCH , di bawah bagian "Transaksi Yang Tidak Terjanjikan dan XACT_STATE", mengatakan:
Kesalahan yang biasanya mengakhiri transaksi di luar blok TRY menyebabkan transaksi memasuki kondisi yang tidak dapat dikomit ketika kesalahan terjadi di dalam blok TRY.
Halaman MSDN untuk SET XACT_ABORT , di bawah bagian "Keterangan", mengatakan:
Ketika SET XACT_ABORT OFF, dalam beberapa kasus hanya pernyataan Transact-SQL yang meningkatkan kesalahan yang dibatalkan dan transaksi terus diproses.
dan:
XACT_ABORT harus diset ON untuk pernyataan modifikasi data dalam transaksi implisit atau eksplisit terhadap sebagian besar penyedia OLE DB, termasuk SQL Server.
Halaman MSDN untuk TRANSAKSI BEGIN , di bawah bagian "Keterangan", mengatakan:
Transaksi lokal yang dimulai oleh pernyataan TRANSAKSI BEGIN ditingkatkan menjadi transaksi terdistribusi jika tindakan berikut dilakukan sebelum pernyataan itu dilakukan atau dibatalkan:
- Pernyataan INSERT, DELETE, atau UPDATE yang mereferensikan tabel jarak jauh pada server yang terhubung dijalankan. Pernyataan INSERT, UPDATE, atau DELETE gagal jika penyedia OLE DB yang digunakan untuk mengakses server yang ditautkan tidak mendukung antarmuka ITransactionJoin.
Penggunaan yang paling berlaku tampaknya berada dalam konteks pernyataan DML Server Terhubung. Dan saya yakin saya mengalami ini bertahun-tahun yang lalu. Saya tidak ingat semua detail, tetapi itu ada hubungannya dengan server jauh tidak tersedia, dan untuk beberapa alasan, kesalahan itu tidak terjebak dalam blok TRY dan tidak pernah dikirim ke CATCH dan begitu juga sebuah KOMIT padahal seharusnya tidak. Tentu saja, itu bisa menjadi masalah tidak XACT_ABORTmengatur ONdaripada gagal untuk memeriksa XACT_STATE(), atau mungkin keduanya. Dan saya ingat pernah membaca sesuatu yang mengatakan jika Anda menggunakan Server Tertaut dan / atau Transaksi Terdistribusi maka Anda perlu menggunakan XACT_ABORT ONdan / atauXACT_STATE() , tetapi sepertinya saya tidak dapat menemukan dokumen itu sekarang. Jika saya menemukannya, saya akan memperbarui ini dengan tautannya.
Namun, saya telah mencoba beberapa hal dan tidak dapat menemukan skenario yang memiliki XACT_ABORT ONdan melewati kontrol ke CATCHblok dengan XACT_STATE()pelaporan 1.
Coba contoh-contoh ini untuk melihat efek XACT_ABORTpada nilai XACT_STATE():
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRAN;
SELECT 1/0 AS [DivideByZero]; -- error, yo!
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK;
END;
END CATCH;
GO ------------------------------------------------
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN;
SELECT 1/0 AS [DivideByZero]; -- error, yo!
COMMIT TRAN;
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK;
END;
END CATCH;
GO ------------------------------------------------
SET XACT_ABORT ON;
BEGIN TRY
SELECT 1/0 AS [DivideByZero]; -- error, yo!
END TRY
BEGIN CATCH
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
XACT_STATE() AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage]
END CATCH;
MEMPERBARUI
Meskipun bukan bagian dari Pertanyaan asli, berdasarkan komentar ini pada Jawaban ini:
Saya telah membaca artikel Erland tentang Penanganan Kesalahan dan Transaksi di mana dia mengatakan itu XACT_ABORTsecara OFFdefault karena alasan warisan dan biasanya kita harus mengaturnya ON.
...
"... jika Anda mengikuti rekomendasi dan menjalankan dengan SET XACT_ABORT AKTIF, transaksi akan selalu hancur."
Sebelum menggunakan di XACT_ABORT ONmana - mana, saya akan bertanya: apa sebenarnya yang diperoleh di sini? Saya belum merasa perlu untuk melakukan dan umumnya menganjurkan Anda harus menggunakannya hanya jika diperlukan. Apakah Anda ingin atau tidak ROLLBACKdapat menangani dengan cukup mudah dengan menggunakan templat yang diperlihatkan dalam jawaban @ Remus , atau yang telah saya gunakan selama bertahun-tahun yang pada dasarnya adalah hal yang sama tetapi tanpa Simpan Point, seperti yang ditunjukkan dalam jawaban ini (yang menangani panggilan bersarang):
Apakah kita diharuskan untuk menangani Transaksi dalam Kode C # dan juga dalam prosedur tersimpan
PEMBARUAN 2
Saya melakukan sedikit pengujian lagi, kali ini dengan membuat .NET Console App kecil, membuat Transaksi di lapisan aplikasi, sebelum mengeksekusi SqlCommandobjek apa pun (yaitu via using (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...), serta menggunakan kesalahan batal-batal, bukan hanya pernyataan kesalahan -aborting, dan menemukan bahwa:
- Transaksi "tidak wajar" adalah Transaksi yang sebagian besar telah dibatalkan (perubahan telah dibatalkan), tetapi
@@TRANCOUNT masih> 0.
- Ketika Anda memiliki "Tidak Terjangkau" Transaksi Anda tidak dapat mengeluarkan
COMMITkarena akan menghasilkan dan kesalahan mengatakan bahwa Transaksi "tidak dapat diterima". Anda juga tidak dapat mengabaikannya / tidak melakukan apa-apa karena kesalahan akan terjadi ketika batch selesai menyatakan bahwa batch selesai dengan transaksi yang masih ada, tidak dapat diterima dan itu akan dibatalkan (jadi, um, jika itu akan dibatalkan otomatis, mengapa repot melempar kesalahan?). Jadi, Anda harus mengeluarkan secara eksplisit ROLLBACK, mungkin tidak langsungCATCH blok , tetapi sebelum batch berakhir.
- Dalam
TRY...CATCHmembangun, ketika XACT_ABORTadalah OFF, kesalahan itu akan mengakhiri transaksi secara otomatis telah mereka terjadi di luar dari TRYblok, seperti kesalahan batch-batal, akan membatalkan pekerjaan tetapi tidak mengakhiri Tranasction, meninggalkan sebagai "uncommitable". Penerbitan a ROLLBACKlebih merupakan formalitas yang diperlukan untuk menutup Transaksi, tetapi pekerjaan telah dibatalkan.
- Ketika
XACT_ABORTadalah ON, sebagian besar kesalahan bertindak sebagai batch-batal, dan karenanya berperilaku seperti yang dijelaskan dalam poin-poin langsung di atas (# 3).
XACT_STATE(), setidaknya dalam satu CATCHblok, akan menunjukkan kesalahan -1untuk batal-batal jika ada Transaksi aktif pada saat kesalahan.
XACT_STATE()terkadang kembali 1bahkan ketika tidak ada Transaksi aktif. Jika @@SPID(antara lain) ada dalam SELECTdaftar bersama XACT_STATE(), maka XACT_STATE()akan mengembalikan 1 ketika tidak ada Transaksi aktif. Perilaku ini dimulai pada SQL Server 2012, dan ada pada 2014, tapi saya belum menguji pada 2016.
Dengan mengingat hal-hal di atas:
- Poin diberikan # 4 dan # 5, karena sebagian besar (atau semua?) Kesalahan akan membuat sebuah transaksi "uncommitable", tampaknya sepenuhnya sia-sia untuk memeriksa
XACT_STATE()di CATCHblok saat XACT_ABORTini ONkarena nilai kembali akan selalu-1 .
- Memeriksa
XACT_STATE()di CATCHblok ketika XACT_ABORTadalah OFFlebih masuk akal karena nilai kembali setidaknya akan memiliki beberapa variasi karena akan kembali 1untuk kesalahan pernyataan-batal. Namun, jika Anda kode seperti kebanyakan dari kita, maka perbedaan ini tidak ada artinya karena Anda akan ROLLBACKtetap menelepon hanya karena fakta bahwa kesalahan terjadi.
- Jika Anda menemukan situasi yang tidak mengeluarkan penerbitan
COMMITdi CATCHblok, maka periksa nilai XACT_STATE(), dan pastikan untuk SET XACT_ABORT OFF;.
XACT_ABORT ONtampaknya menawarkan sedikit atau tidak ada manfaat daripada TRY...CATCHkonstruk.
- Saya tidak dapat menemukan skenario di mana pengecekan
XACT_STATE()memberikan manfaat yang berarti daripada hanya memeriksa @@TRANCOUNT.
- Saya juga tidak dapat menemukan skenario di mana
XACT_STATE()pengembalian 1dalam CATCHblok saat XACT_ABORTini ON. Saya pikir ini adalah kesalahan dokumentasi.
- Ya, Anda dapat memutar kembali Transaksi yang tidak Anda mulai secara eksplisit. Dan dalam konteks penggunaan
XACT_ABORT ON, ini adalah poin yang dapat diperdebatkan karena kesalahan yang terjadi di TRYblok akan secara otomatis memutar balik perubahan.
- The
TRY...CATCHkonstruk memiliki manfaat lebih XACT_ABORT ONdalam tidak secara otomatis membatalkan seluruh transaksi, dan karenanya memungkinkan Transaksi (selama XACT_STATE()pengembalian 1) yang akan dilakukan (bahkan jika ini adalah kasus tepi).
Contoh XACT_STATE()pengembalian -1saat XACT_ABORTadalah OFF:
SET XACT_ABORT OFF;
BEGIN TRY
BEGIN TRAN;
SELECT CONVERT(INT, 'g') AS [ConversionError];
COMMIT TRAN;
END TRY
BEGIN CATCH
DECLARE @State INT;
SET @State = XACT_STATE();
SELECT @@TRANCOUNT AS [@@TRANCOUNT],
@State AS [XactState],
ERROR_MESSAGE() AS [ErrorMessage];
IF (@@TRANCOUNT > 0)
BEGIN
SELECT 'Rollin back...' AS [Transaction];
ROLLBACK;
END;
END CATCH;
PEMBARUAN 3
Terkait dengan item # 6 di bagian UPDATE 2 (yaitu kemungkinan nilai yang salah dikembalikan XACT_STATE()ketika tidak ada Transaksi aktif):
- Perilaku aneh / salah dimulai pada SQL Server 2012 (sejauh ini diuji terhadap 2012 SP2 dan 2014 SP1)
- Dalam SQL Server versi 2005, 2008, dan 2008 R2,
XACT_STATE()tidak melaporkan nilai yang diharapkan ketika digunakan dalam Pemicu atau INSERT...EXECskenario: xact_state () tidak dapat digunakan dengan andal untuk menentukan apakah suatu transaksi akan gagal . Namun, dalam 3 versi ini (saya hanya diuji pada 2008 R2), XACT_STATE()tidak tidak salah melaporkan 1bila digunakan dalam SELECTdengan @@SPID.
Ada bug Connect yang diajukan terhadap perilaku yang disebutkan di sini tetapi ditutup sebagai "Sesuai Desain": XACT_STATE () dapat mengembalikan status transaksi yang salah di SQL 2012 . Namun, tes ini dilakukan ketika memilih dari DMV dan disimpulkan bahwa hal itu secara alami akan memiliki sistem yang menghasilkan transaksi, setidaknya untuk beberapa DMV. Juga dinyatakan dalam tanggapan akhir oleh MS bahwa:
Perhatikan bahwa pernyataan IF, dan juga SELECT tanpa FROM, jangan memulai transaksi.
misalnya, menjalankan SELECT XACT_STATE () jika Anda tidak memiliki transaksi sebelumnya akan mengembalikan 0.
Pernyataan-pernyataan itu salah diberikan contoh berikut:
SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @@SPID AS [SPID];
GO
DECLARE @SPID INT;
SET @SPID = @@SPID;
SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @SPID AS [SPID];
GO
Oleh karena itu, bug Connect baru:
XACT_STATE () mengembalikan 1 ketika digunakan dalam SELECT dengan beberapa variabel sistem tetapi tanpa klausa FROM
PLEASE NOTE bahwa dalam "XACT_STATE () dapat mengembalikan status transaksi yang salah dalam SQL 2012" Hubungkan item yang ditautkan langsung di atas, Microsoft (well, perwakilan dari) menyatakan:
@@ trancount mengembalikan jumlah laporan TRAN BEGIN. Dengan demikian itu bukan indikator yang dapat diandalkan apakah ada transaksi aktif. XACT_STATE () juga mengembalikan 1 jika ada transaksi autocommit aktif, dan dengan demikian merupakan indikator yang lebih dapat diandalkan apakah ada transaksi aktif.
Namun, saya tidak dapat menemukan alasan untuk tidak percaya @@TRANCOUNT. Tes berikut menunjukkan bahwa @@TRANCOUNTmemang kembali 1dalam transaksi komit otomatis:
--- begin setup
GO
CREATE PROCEDURE #TransactionInfo AS
SET NOCOUNT ON;
SELECT @@TRANCOUNT AS [TranCount],
XACT_STATE() AS [XactState];
GO
--- end setup
DECLARE @Test TABLE (TranCount INT, XactState INT);
SELECT * FROM @Test; -- no rows
EXEC #TransactionInfo; -- 0 for both fields
INSERT INTO @Test (TranCount, XactState)
EXEC #TransactionInfo;
SELECT * FROM @Test; -- 1 row; 1 for both fields
Saya juga menguji di atas meja nyata dengan Pemicu dan @@TRANCOUNTdi dalam Pemicu itu melaporkan secara akurat 1meskipun tidak ada Transaksi eksplisit yang telah dimulai.
XACT_ABORTkeONatauOFF.