Чому сервер Sql продовжує виконуватися після помилки підвищення, коли xact_abort увімкнено?


87

Я просто був здивований чимось у TSQL. Я думав, що якщо xact_abort був увімкнений, викликав щось на зразок

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

зупинить виконання збереженої процедури (або будь-якої партії).

Але моє повідомлення про помилку ADO.NET просто довело протилежне. Я отримав як повідомлення про помилку riseer у повідомленні про виняток, так і наступне, що зламалося після цього.

Це мій спосіб вирішення проблеми (що в будь-якому випадку моя звичка), але, здається, це не потрібно:

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

Документи говорять так:

Коли SET XACT_ABORT увімкнено, якщо оператор Transact-SQL викликає помилку під час виконання, вся транзакція припиняється та відкочується назад.

Чи означає це, що я повинен використовувати явну транзакцію?


Просто перевірено і RAISERRORфактично припинить страту, якщо серйозність встановлена ​​на 17 або 18, а не на 16
реформована

2
Тільки що протестували SQL Server 2012 і RAISERRORфактично не припинять виконання, якщо серйозність встановлена ​​на 17 або 18, а не на 16.
Ian Boyd

1
Причину чітко пояснив Ерланд Соммарського (SQL Server MVP з 2001 р.) У частині 2 своєї чудової серії Обробка помилок та транзакцій у SQL Server: "Час від часу у мене виникає відчуття, що SQL Server навмисно розроблений таким чином, щоб коли вони планують новий випуск, вони запитують одне одного, що ми можемо зробити цього разу, щоб заплутати користувачів? Іноді у них трохи не вистачає ідей, але тоді хтось каже: Давайте щось зробимо з обробкою помилок! "
Змінений інженер

@IanBoyd, справді, навіть після встановлення серйозності 17 або 18 або 19 виконання не зупиняється. Що ще цікавіше, якщо ви заглянете у Messagesвкладку, ви побачите відсутність (X rows affected)або PRINTповідомлення, що, я б сказав, повна брехня !
Габріелій

Відповіді:


48

Це By Design TM , як ви можете бачити у Connect за відповіддю команди SQL Server на подібне запитання:

Спасибі за ваш відгук. За задумом параметр набору XACT_ABORT не впливає на поведінку оператора RAISERROR. Ми розглянемо ваш відгук, щоб змінити цю поведінку для майбутнього випуску SQL Server.

Так, це трохи проблема для деяких, хто сподівався, RAISERRORщо високий рівень серйозності (наприклад 16) буде таким самим, як помилка виконання SQL - це не так.

Ваше обхідне рішення стосується лише того, що вам потрібно зробити, і використання явної транзакції не впливає на поведінку, яку ви хочете змінити.


1
Дякую Філіпе. Здається, посилання, на яке ви посилалися, недоступне.
Eric Z Beard

2
Посилання працює нормально, якщо вам коли-небудь потрібно буде його шукати, заголовок "Нехай RAISERROR працює з XACT_ABORT", автор "jorundur", ID: 275308
JohnC

Посилання мертве, без кешованої копії archive.org. Він назавжди загублений для пісків часу.
Ян Бойд

Ця відповідь є гарною резервною копією - із посиланням на документи, де ця поведінка стає зрозумілою.
pcdev

25

Якщо ви використовуєте блок try / catch, номер помилки riser зі ступенем серйозності 11-19 призведе до переходу виконання до блоку catch.

Будь-яка важкість вище 16 є системною помилкою. Для демонстрації наступного коду встановлюється блок try / catch і виконується збережена процедура, яка, як ми вважаємо, не вдасться:

припустимо, у нас є таблиця [dbo]. [Помилки] для утримання помилок припустимо, що у нас є збережена процедура [dbo]. [AssumeThisFails], яка не вдасться виконати її

-- 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

22

Використовуйте RETURNодразу після цього, RAISERROR()і він не буде виконувати процедуру далі.


8
Можливо, вам захочеться зателефонувати rollback transactionперед дзвінком return.
Mike Christian

1
Можливо, вам може знадобитися щось зробити у своєму блоці catch
sqluser

14

Як зазначено в документах до SET XACT_ABORT, THROWзаяву слід використовувати замість RAISERROR.

Вони поводяться дещо по-різному . Але коли XACT_ABORTвстановлено значення ON, ви завжди повинні використовувати THROWкоманду.


25
Якщо у вас немає 2k12 (або вище, коли він виходить), тоді не має бути оператора THROW.
Jeff Moden,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.