Dalam kasus apa transaksi dapat dilakukan dari dalam blok CATCH ketika XACT_ABORT disetel ke ON?

13

Saya telah membaca tentang MSDN TRY...CATCHdan XACT_STATE.

Ini memiliki contoh berikut yang digunakan XACT_STATEdi CATCHblok TRY…CATCHkonstruk 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_STATEkembali?

Harap dicatat, bahwa bendera XACT_ABORTdiatur ke ONdalam contoh.

Jika ada kesalahan yang cukup parah di dalam TRYblok, 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 CATCHdan 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 CATCHdengan transaksi yang dapat dilakukan ketika XACT_ABORTdiatur keON .

Artikel MSDN tentang SET XACT_ABORTmemiliki contoh ketika beberapa pernyataan di dalam transaksi dieksekusi dengan sukses dan beberapa gagal ketika XACT_ABORTdiatur ke OFF, saya mengerti itu. Tetapi, SET XACT_ABORT ONbagaimana mungkin hal itu XACT_STATE()mengembalikan 1 di dalam CATCHblok?

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 ONpara CATCHblok 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
Vladimir Baranov
sumber

Jawaban:

8

Ternyata transaksi tidak dapat dilakukan dari dalam CATCHblok jika XACT_ABORTdiatur ke ON.

Contoh dari MSDN agak menyesatkan, karena cek menyiratkan bahwa XACT_STATEdapat mengembalikan 1 dalam beberapa kasus dan dimungkinkan untuk COMMITtransaksi.

IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;

Itu tidak benar, XACT_STATEtidak akan pernah mengembalikan 1 di dalam CATCHblok jika XACT_ABORTdisetel ke ON.

Tampaknya kode sampel MSDN dimaksudkan untuk mengilustrasikan terutama penggunaan XACT_STATE()fungsi terlepas dari XACT_ABORTpengaturan. Kode sampel terlihat cukup umum untuk berfungsi dengan baik XACT_ABORTdiatur ke ONdan OFF. Hanya saja dengan XACT_ABORT = ONcek IF (XACT_STATE()) = 1menjadi tidak perlu.


Ada seperangkat artikel yang sangat baik tentang Penanganan Kesalahan dan Transaksi di SQL Server oleh Erland Sommarskog. Dalam Bagian 2 - Klasifikasi Kesalahan ia menyajikan tabel komprehensif yang menggabungkan semua kelas kesalahan dan bagaimana mereka ditangani oleh SQL Server dan bagaimana TRY ... CATCHdan XACT_ABORTmengubah perilaku.

+-----------------------------+---------------------------++------------------------------+
|                             |     Without TRY-CATCH     ||        With TRY-CATCH        |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
|              SET XACT_ABORT |  OFF  |  ON   | OFF | ON  ||    ON or OFF     | OFF | ON  |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Class Name                  |    Aborts     |   Rolls   ||    Catchable     |   Dooms   |
|                             |               |   Back    ||                  |transaction|
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Fatal errors                |  Connection   |    Yes    ||       No         |    n/a    |
| Batch-aborting              |     Batch     |    Yes    ||       Yes        |    Yes    |
| Batch-only aborting         |     Batch     | No  | Yes ||       Yes        | No  | Yes |
| Statement-terminating       | Stmnt | Batch | No  | Yes ||       Yes        | No  | Yes |
| Terminates nothing at all   |    Nothing    |    No     ||       Yes        | No  | Yes |
| Compilation: syntax errors  |  (Statement)  |    No     ||       Yes        | No  | Yes |
| Compilation: binding errors | Scope | Batch | No  | Yes || Outer scope only | No  | Yes |
| Compilation: optimisation   |     Batch     |    Yes    || Outer scope only |    Yes    |
| Attention signal            |     Batch     | No  | Yes ||       No         |    n/a    |
| Informational/warning msgs  |    Nothing    |    No     ||       No         |    n/a    |
| Uncatchable errors          |    Varying    |  Varying  ||       No         |    n/a    |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+

Kolom terakhir dalam tabel menjawab pertanyaan. Dengan TRY-CATCHdan dengan XACT_ABORT ONtransaksi akan berakhir dalam semua kasus yang mungkin.

Satu catatan di luar ruang lingkup pertanyaan. Seperti Erlangga mengatakan, konsistensi ini adalah salah satu alasan untuk set XACT_ABORTke ON:

Saya sudah memberikan rekomendasi bahwa prosedur tersimpan Anda harus menyertakan perintah SET XACT_ABORT, NOCOUNT ON. Jika Anda melihat tabel di atas, Anda melihat bahwa dengan XACT_ABORTefeknya, ada beberapa tingkat konsistensi yang lebih tinggi. Misalnya, transaksi selalu hancur. Berikut ini, saya akan menunjukkan banyak contoh di mana saya ditetapkan XACT_ABORTuntuk OFF, sehingga Anda bisa mendapatkan pemahaman tentang mengapa Anda harus menghindari pengaturan default ini.

Vladimir Baranov
sumber
7

Saya akan mendekati ini secara berbeda. XACT_ABORT_ONadalah palu godam, Anda dapat menggunakan pendekatan yang lebih halus, lihat Penanganan pengecualian dan transaksi bersarang :

create procedure [usp_my_procedure_name]
as
begin
    set nocount on;
    declare @trancount int;
    set @trancount = @@trancount;
    begin try
        if @trancount = 0
            begin transaction
        else
            save transaction usp_my_procedure_name;

        -- Do the actual work here

lbexit:
        if @trancount = 0   
            commit;
    end try
    begin catch
        declare @error int, @message varchar(4000), @xstate int;
        select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
        if @xstate = -1
            rollback;
        if @xstate = 1 and @trancount = 0
            rollback
        if @xstate = 1 and @trancount > 0
            rollback transaction usp_my_procedure_name;

        raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
    end catch   
end
go

Pendekatan ini akan mengembalikan, jika mungkin, hanya pekerjaan yang dilakukan di dalam blok TRY, dan mengembalikan status ke status sebelum memasuki blok TRY. Dengan cara ini Anda dapat melakukan pemrosesan yang kompleks, seperti iterasi kursor, tanpa kehilangan semua pekerjaan jika terjadi kesalahan. Satu-satunya kelemahan adalah bahwa, dengan menggunakan savepoint transaksi, Anda dilarang menggunakan apa pun yang tidak kompatibel dengan savepoint, seperti transaksi terdistribusi.

Remus Rusanu
sumber
Saya menghargai balasan Anda, tetapi pertanyaannya adalah tidak benar-benar apakah kita harus mengatur XACT_ABORTke ONatau OFF.
Vladimir Baranov
7

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:

  1. 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.
  2. memeriksa XACT_STATE()di CATCHblok benar-benar hanya masuk akal jika kedua berikut ini benar:
    1. XACT_ABORTadalah OFF(yang lain XACT_STATE()harus selalu kembali -1dan @@TRANCOUNTakan menjadi semua yang Anda butuhkan)
    2. 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.
  3. 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.
  4. 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:

  1. Transaksi "tidak wajar" adalah Transaksi yang sebagian besar telah dibatalkan (perubahan telah dibatalkan), tetapi @@TRANCOUNT masih> 0.
  2. 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.
  3. 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.
  4. Ketika XACT_ABORTadalah ON, sebagian besar kesalahan bertindak sebagai batch-batal, dan karenanya berperilaku seperti yang dijelaskan dalam poin-poin langsung di atas (# 3).
  5. XACT_STATE(), setidaknya dalam satu CATCHblok, akan menunjukkan kesalahan -1untuk batal-batal jika ada Transaksi aktif pada saat kesalahan.
  6. 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.

Solomon Rutzky
sumber
4

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.

Max Vernon
sumber
Terima kasih atas balasannya Saya hanya tidak bisa memikirkan kasus ketika sederhana ROLLBACKtidak akan bekerja di dalam CATCHdan Anda memberi contoh yang baik. Saya kira, itu juga dapat dengan cepat menjadi berantakan jika transaksi bersarang dan prosedur tersimpan bersarang dengan mereka sendiri TRY ... CATCH ... ROLLBACKterlibat.
Vladimir Baranov
Namun, saya akan sangat menghargainya, jika Anda dapat memperluas jawaban Anda mengenai bagian kedua - IF (XACT_STATE()) = 1 COMMIT TRANSACTION; Bagaimana kita bisa berakhir di dalam CATCHblok dengan transaksi yang dapat diterima? Saya tidak akan berani melakukan beberapa (mungkin) sampah dari dalam CATCH. Alasan saya adalah: jika kita berada di dalam CATCHsesuatu yang salah, saya tidak bisa mempercayai keadaan database, jadi saya akan lebih baik ROLLBACKapa pun yang kita punya.
Vladimir Baranov