TL; DR / Ringkasan Eksekutif: Mengenai bagian Pertanyaan ini:
Saya tidak melihat dalam kasus apa kontrol dapat diteruskan ke dalam CATCH
dengan transaksi yang dapat dilakukan ketika XACT_ABORT
diatur keON
.
Saya telah melakukan cukup banyak pengujian pada ini sekarang dan saya tidak dapat menemukan kasus di mana XACT_STATE()
kembali 1
dalam CATCH
blok kapan @@TRANCOUNT > 0
dan properti sesi XACT_ABORT
adalah 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_ABORT
memiliki contoh ketika beberapa pernyataan di dalam transaksi dieksekusi dengan sukses dan beberapa gagal ketika XACT_ABORT
diatur keOFF
Benar, tetapi pernyataan dalam contoh itu tidak berada dalam TRY
blok. Pernyataan-pernyataan yang sama dalam TRY
blok masih akan mencegah eksekusi untuk setiap pernyataan setelah satu yang menyebabkan kesalahan, tetapi dengan asumsi bahwa XACT_ABORT
adalah OFF
, ketika kontrol akan diteruskan ke CATCH
blok 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_ABORT
ada ON
maka perubahan sebelumnya secara otomatis dibatalkan, dan kemudian Anda diberikan pilihan untuk: a) menerbitkanROLLBACK
yang kebanyakan hanya penerimaan situasi sejak Transaksi itu sudah digulung kembali dikurangi ulang @@TRANCOUNT
ke 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_ABORT
adalah bahwa properti sesi ini, dan contoh kode itu, telah ada sejak SQL Server 2000 (dokumentasi hampir identik di antara versi), mendahului TRY...CATCH
konstruk yang sebelumnya diperkenalkan di SQL Server 2005. melihat dokumentasi yang lagi dan melihat contoh ( tanpa yang TRY...CATCH
), menggunakan XACT_ABORT ON
penyebab 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_ABORT
dokumentasi itu).
Saya pikir masuk akal untuk menyimpulkan bahwa:
- pengenalan
TRY...CATCH
konstruk 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 CATCH
blok benar-benar hanya masuk akal jika kedua berikut ini benar:
XACT_ABORT
adalah OFF
(yang lain XACT_STATE()
harus selalu kembali -1
dan @@TRANCOUNT
akan menjadi semua yang Anda butuhkan)
- Anda memiliki logika di
CATCH
blok, atau di suatu tempat di atas rantai jika panggilan bersarang, yang membuat perubahan ( COMMIT
atau 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...CATCH
konstruk dalam SQL Server 2005, untuk sebagian besar, mengacaukan XACT_ABORT ON
properti 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 ON
menyediakan cara yang mudah dan dapat diandalkan untuk berhenti memproses ketika kesalahan terjadi, dibandingkan dengan memeriksa @@ERROR
setelah setiap pernyataan.
- Dokumentasi contoh kode untuk
XACT_STATE()
adalah salah, atau paling menyesatkan, dalam hal ini menunjukkan memeriksa XACT_STATE() = 1
saat XACT_ABORT
ini 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 CATCH
blok saat XACT_ABORT
ini 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 COMMIT
atau 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 ROLLBACK
karena 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 ON
perubahan sedikit. Anda dapat memiliki kesalahan biasa, setelah melakukan BEGIN TRAN
itu akan melewati kontrol ke blok CATCH saat XACT_ABORT
ini OFF
dan 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 -1
saat XACT_ABORT
ini 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_ABORT
mengatur ON
daripada 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 ON
dan / 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 ON
dan melewati kontrol ke CATCH
blok dengan XACT_STATE()
pelaporan 1
.
Coba contoh-contoh ini untuk melihat efek XACT_ABORT
pada 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_ABORT
secara OFF
default 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 ON
mana - 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 ROLLBACK
dapat 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 SqlCommand
objek 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
COMMIT
karena 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...CATCH
membangun, ketika XACT_ABORT
adalah OFF
, kesalahan itu akan mengakhiri transaksi secara otomatis telah mereka terjadi di luar dari TRY
blok, seperti kesalahan batch-batal, akan membatalkan pekerjaan tetapi tidak mengakhiri Tranasction, meninggalkan sebagai "uncommitable". Penerbitan a ROLLBACK
lebih merupakan formalitas yang diperlukan untuk menutup Transaksi, tetapi pekerjaan telah dibatalkan.
- Ketika
XACT_ABORT
adalah 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 CATCH
blok, akan menunjukkan kesalahan -1
untuk batal-batal jika ada Transaksi aktif pada saat kesalahan.
XACT_STATE()
terkadang kembali 1
bahkan ketika tidak ada Transaksi aktif. Jika @@SPID
(antara lain) ada dalam SELECT
daftar 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 CATCH
blok saat XACT_ABORT
ini ON
karena nilai kembali akan selalu-1
.
- Memeriksa
XACT_STATE()
di CATCH
blok ketika XACT_ABORT
adalah OFF
lebih masuk akal karena nilai kembali setidaknya akan memiliki beberapa variasi karena akan kembali 1
untuk kesalahan pernyataan-batal. Namun, jika Anda kode seperti kebanyakan dari kita, maka perbedaan ini tidak ada artinya karena Anda akan ROLLBACK
tetap menelepon hanya karena fakta bahwa kesalahan terjadi.
- Jika Anda menemukan situasi yang tidak mengeluarkan penerbitan
COMMIT
di CATCH
blok, maka periksa nilai XACT_STATE()
, dan pastikan untuk SET XACT_ABORT OFF;
.
XACT_ABORT ON
tampaknya menawarkan sedikit atau tidak ada manfaat daripada TRY...CATCH
konstruk.
- 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 1
dalam CATCH
blok saat XACT_ABORT
ini 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 TRY
blok akan secara otomatis memutar balik perubahan.
- The
TRY...CATCH
konstruk memiliki manfaat lebih XACT_ABORT ON
dalam 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 -1
saat XACT_ABORT
adalah 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...EXEC
skenario: 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 1
bila digunakan dalam SELECT
dengan @@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 @@TRANCOUNT
memang kembali 1
dalam 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 @@TRANCOUNT
di dalam Pemicu itu melaporkan secara akurat 1
meskipun tidak ada Transaksi eksplisit yang telah dimulai.
XACT_ABORT
keON
atauOFF
.