Saya telah membaca tentang MSDN TRY...CATCH
dan XACT_STATE
.
Ini memiliki contoh berikut yang digunakan XACT_STATE
di CATCH
blok TRY…CATCH
konstruk untuk menentukan apakah akan melakukan atau mengembalikan transaksi:
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Test XACT_STATE for 0, 1, or -1.
-- If 1, the transaction is committable.
-- If -1, the transaction is uncommittable and should
-- be rolled back.
-- XACT_STATE = 0 means there is no transaction and
-- a commit or rollback operation would generate an error.
-- Test whether the transaction is uncommittable.
IF (XACT_STATE()) = -1
BEGIN
PRINT 'The transaction is in an uncommittable state.' +
' Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
-- Test whether the transaction is active and valid.
IF (XACT_STATE()) = 1
BEGIN
PRINT 'The transaction is committable.' +
' Committing transaction.'
COMMIT TRANSACTION;
END;
END CATCH;
GO
Yang tidak saya mengerti adalah, mengapa saya harus peduli dan memeriksa apa yang XACT_STATE
kembali?
Harap dicatat, bahwa bendera XACT_ABORT
diatur ke ON
dalam contoh.
Jika ada kesalahan yang cukup parah di dalam TRY
blok, kontrol akan masuk 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?
Tetapi, contoh dari MSDN ini menyiratkan bahwa mungkin ada kasus ketika kontrol dilewatkan CATCH
dan masih masuk akal untuk melakukan transaksi. Bisakah seseorang memberikan beberapa contoh praktis kapan itu bisa terjadi, kapan itu masuk akal?
Saya tidak melihat dalam kasus apa kontrol dapat diteruskan ke dalam CATCH
dengan transaksi yang dapat dilakukan ketika XACT_ABORT
diatur keON
.
Artikel MSDN tentang SET XACT_ABORT
memiliki contoh ketika beberapa pernyataan di dalam transaksi dieksekusi dengan sukses dan beberapa gagal ketika XACT_ABORT
diatur ke OFF
, saya mengerti itu. Tetapi, SET XACT_ABORT ON
bagaimana mungkin hal itu XACT_STATE()
mengembalikan 1 di dalam CATCH
blok?
Awalnya, saya akan menulis kode ini seperti ini:
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Some severe problem with the transaction
PRINT 'Rolling back transaction.';
ROLLBACK TRANSACTION;
END CATCH;
GO
Memperhatikan jawaban Max Vernon, saya akan menulis kode seperti ini. Dia menunjukkan bahwa masuk akal untuk memeriksa apakah ada transaksi aktif sebelum mencoba ROLLBACK
. Namun, dengan SET XACT_ABORT ON
para CATCH
blok dapat memiliki baik ditakdirkan transaksi atau tidak ada transaksi sama sekali. Jadi, bagaimanapun juga tidak ada COMMIT
. Apakah aku salah?
USE AdventureWorks2012;
GO
-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- A FOREIGN KEY constraint exists on this table. This
-- statement will generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
-- If the delete operation succeeds, commit the transaction. The CATCH
-- block will not execute.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Some severe problem with the transaction
IF (XACT_STATE()) <> 0
BEGIN
-- There is still an active transaction that should be rolled back
PRINT 'Rolling back transaction.';
ROLLBACK TRANSACTION;
END;
END CATCH;
GO
sumber
XACT_ABORT
keON
atauOFF
.TL; DR / Ringkasan Eksekutif: Mengenai bagian Pertanyaan ini:
Saya telah melakukan cukup banyak pengujian pada ini sekarang dan saya tidak dapat menemukan kasus di mana
XACT_STATE()
kembali1
dalamCATCH
blok kapan@@TRANCOUNT > 0
dan properti sesiXACT_ABORT
adalahON
. Dan pada kenyataannya, menurut halaman MSDN saat ini untuk SET XACT_ABORT :Pernyataan itu tampaknya sesuai dengan spekulasi Anda dan temuan saya.
Benar, tetapi pernyataan dalam contoh itu tidak berada dalam
TRY
blok. Pernyataan-pernyataan yang sama dalamTRY
blok masih akan mencegah eksekusi untuk setiap pernyataan setelah satu yang menyebabkan kesalahan, tetapi dengan asumsi bahwaXACT_ABORT
adalahOFF
, ketika kontrol akan diteruskan keCATCH
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, jikaXACT_ABORT
adaON
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
ke0
, 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), mendahuluiTRY...CATCH
konstruk yang sebelumnya diperkenalkan di SQL Server 2005. melihat dokumentasi yang lagi dan melihat contoh ( tanpa yangTRY...CATCH
), menggunakanXACT_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" dalamSET XACT_ABORT
dokumentasi itu).Saya pikir masuk akal untuk menyimpulkan bahwa:
TRY...CATCH
konstruk dalam SQL Server 2005 menciptakan kebutuhan untuk status Transaksi baru (yaitu "tidak dapat dikomit") danXACT_STATE()
fungsi untuk mendapatkan informasi itu.XACT_STATE()
diCATCH
blok benar-benar hanya masuk akal jika kedua berikut ini benar:XACT_ABORT
adalahOFF
(yang lainXACT_STATE()
harus selalu kembali-1
dan@@TRANCOUNT
akan menjadi semua yang Anda butuhkan)CATCH
blok, atau di suatu tempat di atas rantai jika panggilan bersarang, yang membuat perubahan (COMMIT
atau bahkan pernyataan DML, DDL, dll) daripada melakukanROLLBACK
. (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 memeriksaXACT_STATE()
bukan@@TRANCOUNT
, dan mengapa pengujian menunjukkan bahwa alasan mereka tidak berjalan.TRY...CATCH
konstruk dalam SQL Server 2005, untuk sebagian besar, mengacaukanXACT_ABORT ON
properti sesi karena menyediakan tingkat kontrol yang lebih besar atas Transaksi (Anda setidaknya memiliki pilihan untukCOMMIT
, asalkanXACT_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.XACT_STATE()
adalah salah, atau paling menyesatkan, dalam hal ini menunjukkan memeriksaXACT_STATE() = 1
saatXACT_ABORT
iniON
.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 saatXACT_ABORT
iniON
), atau bahkan jika mungkin, masih berfokus pada kemungkinan teknis yang sedikit orang akan inginkan atau butuhkan, dan mengabaikan alasan seseorang lebih mungkin membutuhkannya.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 a1
, itu berarti bahwa Transaksi "layak", bahwa Anda memiliki pilihan antaraCOMMIT
atauROLLBACK
. 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 perluROLLBACK
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 melakukanBEGIN TRAN
itu akan melewati kontrol ke blok CATCH saatXACT_ABORT
iniOFF
dan XACT_STATE () akan kembali1
. TETAPI, jika XACT_ABORT adalahON
, maka Transaksi itu "dibatalkan" (yaitu tidak valid) untuk kesalahan apa pun dan kemudianXACT_STATE()
akan kembali-1
. Dalam hal ini, tampaknya tidak berguna untuk memeriksaXACT_STATE()
di dalamCATCH
blok seperti yang selalu tampaknya mengembalikan-1
saatXACT_ABORT
iniON
.Jadi apa itu
XACT_STATE()
? Beberapa petunjuk adalah:Halaman MSDN untuk
TRY...CATCH
, di bawah bagian "Transaksi Yang Tidak Terjanjikan dan XACT_STATE", mengatakan:Halaman MSDN untuk SET XACT_ABORT , di bawah bagian "Keterangan", mengatakan:
dan:
Halaman MSDN untuk TRANSAKSI BEGIN , di bawah bagian "Keterangan", mengatakan:
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
mengaturON
daripada gagal untuk memeriksaXACT_STATE()
, atau mungkin keduanya. Dan saya ingat pernah membaca sesuatu yang mengatakan jika Anda menggunakan Server Tertaut dan / atau Transaksi Terdistribusi maka Anda perlu menggunakanXACT_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 keCATCH
blok denganXACT_STATE()
pelaporan1
.Coba contoh-contoh ini untuk melihat efek
XACT_ABORT
pada nilaiXACT_STATE()
:MEMPERBARUI
Meskipun bukan bagian dari Pertanyaan asli, berdasarkan komentar ini pada Jawaban ini:
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 tidakROLLBACK
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 viausing (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...
), serta menggunakan kesalahan batal-batal, bukan hanya pernyataan kesalahan -aborting, dan menemukan bahwa:@@TRANCOUNT
masih> 0.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 eksplisitROLLBACK
, mungkin tidak langsungCATCH
blok , tetapi sebelum batch berakhir.TRY...CATCH
membangun, ketikaXACT_ABORT
adalahOFF
, kesalahan itu akan mengakhiri transaksi secara otomatis telah mereka terjadi di luar dariTRY
blok, seperti kesalahan batch-batal, akan membatalkan pekerjaan tetapi tidak mengakhiri Tranasction, meninggalkan sebagai "uncommitable". Penerbitan aROLLBACK
lebih merupakan formalitas yang diperlukan untuk menutup Transaksi, tetapi pekerjaan telah dibatalkan.XACT_ABORT
adalahON
, sebagian besar kesalahan bertindak sebagai batch-batal, dan karenanya berperilaku seperti yang dijelaskan dalam poin-poin langsung di atas (# 3).XACT_STATE()
, setidaknya dalam satuCATCH
blok, akan menunjukkan kesalahan-1
untuk batal-batal jika ada Transaksi aktif pada saat kesalahan.XACT_STATE()
terkadang kembali1
bahkan ketika tidak ada Transaksi aktif. Jika@@SPID
(antara lain) ada dalamSELECT
daftar bersamaXACT_STATE()
, makaXACT_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:
XACT_STATE()
diCATCH
blok saatXACT_ABORT
iniON
karena nilai kembali akan selalu-1
.XACT_STATE()
diCATCH
blok ketikaXACT_ABORT
adalahOFF
lebih masuk akal karena nilai kembali setidaknya akan memiliki beberapa variasi karena akan kembali1
untuk kesalahan pernyataan-batal. Namun, jika Anda kode seperti kebanyakan dari kita, maka perbedaan ini tidak ada artinya karena Anda akanROLLBACK
tetap menelepon hanya karena fakta bahwa kesalahan terjadi.COMMIT
diCATCH
blok, maka periksa nilaiXACT_STATE()
, dan pastikan untukSET XACT_ABORT OFF;
.XACT_ABORT ON
tampaknya menawarkan sedikit atau tidak ada manfaat daripadaTRY...CATCH
konstruk.XACT_STATE()
memberikan manfaat yang berarti daripada hanya memeriksa@@TRANCOUNT
.XACT_STATE()
pengembalian1
dalamCATCH
blok saatXACT_ABORT
iniON
. Saya pikir ini adalah kesalahan dokumentasi.XACT_ABORT ON
, ini adalah poin yang dapat diperdebatkan karena kesalahan yang terjadi diTRY
blok akan secara otomatis memutar balik perubahan.TRY...CATCH
konstruk memiliki manfaat lebihXACT_ABORT ON
dalam tidak secara otomatis membatalkan seluruh transaksi, dan karenanya memungkinkan Transaksi (selamaXACT_STATE()
pengembalian1
) yang akan dilakukan (bahkan jika ini adalah kasus tepi).Contoh
XACT_STATE()
pengembalian-1
saatXACT_ABORT
adalahOFF
:PEMBARUAN 3
Terkait dengan item # 6 di bagian UPDATE 2 (yaitu kemungkinan nilai yang salah dikembalikan
XACT_STATE()
ketika tidak ada Transaksi aktif):XACT_STATE()
tidak melaporkan nilai yang diharapkan ketika digunakan dalam Pemicu atauINSERT...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 melaporkan1
bila digunakan dalamSELECT
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:
Pernyataan-pernyataan itu salah diberikan contoh berikut:
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:
Namun, saya tidak dapat menemukan alasan untuk tidak percaya
@@TRANCOUNT
. Tes berikut menunjukkan bahwa@@TRANCOUNT
memang kembali1
dalam transaksi komit otomatis:Saya juga menguji di atas meja nyata dengan Pemicu dan
@@TRANCOUNT
di dalam Pemicu itu melaporkan secara akurat1
meskipun tidak ada Transaksi eksplisit yang telah dimulai.sumber
Pemrograman defensif mengharuskan Anda menulis kode yang menangani sebanyak mungkin kondisi yang diketahui, sehingga mengurangi kemungkinan bug.
Memeriksa XACT_STATE () untuk menentukan apakah kemunduran dapat dijalankan hanyalah praktik yang baik. Mencoba melakukan rollback secara buta berarti Anda dapat secara tidak sengaja menyebabkan kesalahan di dalam TRY ... CATCH Anda.
Salah satu cara kemunduran mungkin gagal di dalam TRY ... CATCH akan terjadi jika Anda tidak secara eksplisit memulai transaksi. Menyalin dan menempelkan blok kode mungkin dengan mudah menyebabkan ini.
sumber
ROLLBACK
tidak akan bekerja di dalamCATCH
dan Anda memberi contoh yang baik. Saya kira, itu juga dapat dengan cepat menjadi berantakan jika transaksi bersarang dan prosedur tersimpan bersarang dengan mereka sendiriTRY ... CATCH ... ROLLBACK
terlibat.IF (XACT_STATE()) = 1 COMMIT TRANSACTION;
Bagaimana kita bisa berakhir di dalamCATCH
blok dengan transaksi yang dapat diterima? Saya tidak akan berani melakukan beberapa (mungkin) sampah dari dalamCATCH
. Alasan saya adalah: jika kita berada di dalamCATCH
sesuatu yang salah, saya tidak bisa mempercayai keadaan database, jadi saya akan lebih baikROLLBACK
apa pun yang kita punya.