Mengapa Sql Server tetap menjalankan setelah raiserror saat xact_abort aktif?

88

Saya baru saja terkejut dengan sesuatu di TSQL. Saya pikir jika xact_abort aktif, memanggil sesuatu seperti

raiserror('Something bad happened', 16, 1);

akan menghentikan eksekusi prosedur yang disimpan (atau batch apa pun).

Tapi pesan kesalahan ADO.NET saya justru membuktikan sebaliknya. Saya mendapat pesan kesalahan penyebab kesalahan dalam pesan pengecualian, ditambah hal berikutnya yang rusak setelah itu.

Ini adalah solusi saya (yang merupakan kebiasaan saya), tetapi sepertinya itu tidak perlu:

if @somethingBadHappened
    begin;
        raiserror('Something bad happened', 16, 1);
        return;
    end;

Dokumen mengatakan ini:

Ketika SET XACT_ABORT AKTIF, jika pernyataan Transact-SQL memunculkan kesalahan run-time, seluruh transaksi dihentikan dan dibatalkan.

Apakah itu berarti saya harus menggunakan transaksi eksplisit?

Eric Z Beard
sumber
Baru saja diuji dan RAISERRORsebenarnya akan menghentikan eksekusi jika tingkat keparahan disetel ke 17 atau 18, bukan 16
direformasi
2
Baru saja menguji SQL Server 2012 dan RAISERRORsebenarnya tidak akan menghentikan eksekusi jika tingkat keparahan disetel ke 17 atau 18, bukan 16.
Ian Boyd
1
Alasannya dijelaskan dengan jelas oleh Erland Sommarskog (SQL Server MVP sejak 2001) di bagian 2 dari seri Penanganan Kesalahan dan Transaksi yang sangat baik di SQL Server: "Sesekali, saya merasa bahwa SQL Server sengaja dirancang untuk menjadi seperti membingungkan mungkin. Ketika mereka merencanakan rilis baru, mereka saling bertanya apa yang dapat kami lakukan kali ini untuk membingungkan pengguna? Terkadang mereka kehabisan ide, tetapi kemudian seseorang berkata Ayo lakukan sesuatu dengan penanganan kesalahan! "
Reversed Engineer
1
@IanBoyd, memang, bahkan setelah menetapkan tingkat keparahan ke 17 atau 18 atau 19 eksekusi tidak dihentikan. Yang lebih menarik, jika Anda melihat di Messagestab Anda tidak akan melihat (X rows affected)atau PRINTpesan, yang menurut saya adalah kebohongan total !
Gabrielius

Jawaban:

48

Ini adalah By Design TM , seperti yang Anda lihat di Connect by the SQL Server tim menanggapi pertanyaan serupa:

Terima kasih atas tanggapan Anda. Secara desain, opsi set XACT_ABORT tidak memengaruhi perilaku pernyataan RAISERROR. Kami akan mempertimbangkan umpan balik Anda untuk mengubah perilaku ini untuk rilis SQL Server di masa mendatang.

Ya, ini sedikit masalah bagi beberapa orang yang berharap RAISERRORdengan tingkat keparahan tinggi (seperti 16) akan sama dengan kesalahan eksekusi SQL - ternyata tidak.

Solusi Anda hanya tentang apa yang perlu Anda lakukan, dan menggunakan transaksi eksplisit tidak berpengaruh pada perilaku yang ingin Anda ubah.

Philip Rieck
sumber
1
Terima kasih Philip. Tautan yang Anda referensikan sepertinya tidak tersedia.
Eric Z Beard
2
Tautan berfungsi dengan baik, jika Anda perlu mencarinya, judul "Minta RAISERROR bekerja dengan XACT_ABORT", penulis "jorundur", ID: 275308
JohnC
Tautan sudah mati, tanpa salinan cache archive.org. Sudah hilang ke pasir waktu selamanya.
Ian Boyd
Jawaban ini adalah cadangan yang bagus - dengan tautan ke dokumen tempat perilaku ini dijelaskan.
pcdev
25

Jika Anda menggunakan blok try / catch, nomor kesalahan raiserror dengan tingkat keparahan 11-19 akan menyebabkan eksekusi melompat ke blok catch.

Setiap tingkat keparahan di atas 16 adalah kesalahan sistem. Untuk mendemonstrasikan kode berikut, siapkan blok coba / tangkap dan jalankan prosedur tersimpan yang kami anggap akan gagal:

asumsikan kita memiliki tabel [dbo]. [Kesalahan] untuk menahan kesalahan asumsikan kita memiliki prosedur tersimpan [dbo]. [AssumeThisFails] yang akan gagal saat kita menjalankannya

-- first lets build a temporary table to hold errors
if (object_id('tempdb..#RAISERRORS') is null)
 create table #RAISERRORS (ErrorNumber int, ErrorMessage varchar(400), ErrorSeverity int, ErrorState int, ErrorLine int, ErrorProcedure varchar(128));

-- this will determine if the transaction level of the query to programatically determine if we need to begin a new transaction or create a save point to rollback to
declare @tc as int;
set @tc = @@trancount;
if (@tc = 0)
 begin transaction;
else
 save transaction myTransaction;

-- the code in the try block will be executed
begin try
 declare @return_value = '0';
 set @return_value = '0';
 declare
  @ErrorNumber as int,
  @ErrorMessage as varchar(400),
  @ErrorSeverity as int,
  @ErrorState as int,
  @ErrorLine as int,
  @ErrorProcedure as varchar(128);


 -- assume that this procedure fails...
 exec @return_value = [dbo].[AssumeThisFails]
 if (@return_value <> 0)
  raiserror('This is my error message', 17, 1);

 -- the error severity of 17 will be considered a system error execution of this query will skip the following statements and resume at the begin catch block
 if (@tc = 0)
  commit transaction;
 return(0);
end try


-- the code in the catch block will be executed on raiserror("message", 17, 1)
begin catch
  select
   @ErrorNumber = ERROR_NUMBER(),
   @ErrorMessage = ERROR_MESSAGE(),
   @ErrorSeverity = ERROR_SEVERITY(),
   @ErrorState = ERROR_STATE(),
   @ErrorLine = ERROR_LINE(),
   @ErrorProcedure = ERROR_PROCEDURE();

  insert #RAISERRORS (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
   values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);

  -- if i started the transaction
  if (@tc = 0)
  begin
   if (XACT_STATE() <> 0)
   begin
     select * from #RAISERRORS;
    rollback transaction;
    insert into [dbo].[Errors] (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     select * from #RAISERRORS;
    insert [dbo].[Errors] (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);
    return(1);
   end
  end
  -- if i didn't start the transaction
  if (XACT_STATE() = 1)
  begin
   rollback transaction myTransaction;
   if (object_id('tempdb..#RAISERRORS') is not null)
    insert #RAISERRORS (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);
   else
    raiserror(@ErrorMessage, @ErrorSeverity, @ErrorState);
   return(2); 
  end
  else if (XACT_STATE() = -1)
  begin
   rollback transaction;
   if (object_id('tempdb..#RAISERRORS') is not null)
    insert #RAISERRORS (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);
   else
    raiserror(@ErrorMessage, @ErrorSeverity, @ErrorState);
   return(3);
  end
 end catch
end
ninegrid.dll
sumber
22

Gunakan RETURNsegera setelahnya RAISERROR()dan itu tidak akan menjalankan prosedur lebih lanjut.

piyush.dll
sumber
8
Anda mungkin ingin menelepon rollback transactionsebelum menelepon return.
Mike Christian
1
Mungkin Anda perlu melakukan sesuatu di blok tangkapan Anda
sqluser
15

Seperti yang ditunjukkan di dokumen untuk SET XACT_ABORT, THROWpernyataan itu harus digunakan sebagai pengganti RAISERROR.

Keduanya berperilaku sedikit berbeda . Namun bila XACT_ABORTdisetel ke ON, maka Anda harus selalu menggunakan THROWperintah.

Möoz
sumber
25
Jika Anda tidak memiliki 2k12 (atau lebih saat keluar), maka tidak ada pernyataan THROW yang bisa didapat.
Jeff Moden