Почему Sql Server продолжает выполняться после raiserror, когда xact_abort включен?

87

Меня только что кое-что удивило в TSQL. Я думал, что если был включен xact_abort, вызывая что-то вроде

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

остановит выполнение хранимой процедуры (или любого пакета).

Но мое сообщение об ошибке ADO.NET просто доказывает обратное. Я получил как сообщение об ошибке raiserror в сообщении об исключении, так и следующее, что сломалось после этого.

Это мой обходной путь (в любом случае это моя привычка), но не похоже, что это необходимо:

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

В документах говорится следующее:

Если для SET XACT_ABORT установлено значение ON, если инструкция Transact-SQL вызывает ошибку времени выполнения, вся транзакция завершается и выполняется откат.

Означает ли это, что я должен использовать явную транзакцию?

Эрик Зи Берд
источник
Только что протестировано и RAISERRORфактически прекратит выполнение, если уровень серьезности установлен на 17 или 18 вместо 16.
изменено
2
Только что протестировал SQL Server 2012 и RAISERRORфактически не прерывает выполнение, если уровень серьезности установлен на 17 или 18 вместо 16.
Ян Бойд,
1
Причину этого ясно объясняет Эрланд Соммарског (SQL Server MVP с 2001 г.) во второй части своей превосходной серии «Обработка ошибок и транзакций в SQL Server»: «Время от времени у меня возникает ощущение, что SQL Server намеренно разработан таким образом, чтобы сбивает с толку. Когда они планируют выпуск нового релиза, они спрашивают друг друга, что мы можем сделать на этот раз, чтобы запутать пользователей? Иногда у них немного заканчиваются идеи, но затем кто-то говорит: «Давайте что-нибудь с обработкой ошибок!
Обратный инженер
1
@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
Ссылка работает нормально, если когда - либо вам нужно искать его, название «Have RAISERROR работы с XACT_ABORT», автор «jorundur», ID: 275308
JohnC
Ссылка мертва, кешированная копия на archive.org отсутствует. Это навсегда потеряно в песках времени.
Ian Boyd
Этот ответ является хорошей резервной копией - со ссылкой на документы, где это поведение поясняется.
pcdev
25

Если вы используете блок try / catch, номер ошибки повышения уровня серьезности 11-19 приведет к переходу выполнения к блоку catch.

Любая серьезность выше 16 является системной ошибкой. Чтобы продемонстрировать следующий код, настраивает блок try / catch и выполняет хранимую процедуру, которая, как мы предполагаем, завершится ошибкой:

предположим, что у нас есть таблица [dbo]. [Errors] для хранения ошибок предположим, что у нас есть хранимая процедура [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.
Майк Кристиан
1
Возможно, вам нужно будет что-то сделать в вашем блоке catch
sqluser
14

Как указано в документации для SET XACT_ABORT, THROWоператор следует использовать вместо RAISERROR.

Эти двое ведут себя немного по-разному . Но когда XACT_ABORTустановлено значение ON, вы всегда должны использовать THROWкоманду.

Möoz
источник
25
Если у вас нет 2k12 (или выше, когда он выйдет), то нет оператора THROW.
Джефф Моден,