Обработка ошибок в процедуре TSQL - PullRequest
0 голосов
/ 05 марта 2012

РЕЗЮМЕ ПРОБЛЕМЫ:

Я сделал обработку ошибок, которая кажется слишком сложной и все еще не решает все ситуации (так как могут быть ситуации, когда транзакция переходит в состояние uncommitable).Я подозреваю, что я:

  • пропустил что-то важное и сделал что-то не так (вы можете объяснить, что? И как мне тогда это сделать?).
  • ничего не пропустил - простоЯ должен признать, что обработка ошибок все еще является огромной проблемой в SQL Server.

Можете ли вы предложить лучшее решение (для описанной ниже ситуации)?

О МОЕЙ СИТУАЦИИ:

У меня есть (пара) хранимых процедур в SQL Server, которые вызываются из разных мест.Можно обобщить для двух ситуаций:

  1. Процедура вызывается из кода .NET, транзакция выполняется и обрабатывается в процедуре SQL
  2. Процедура вызывается в другой процедуре (точнее, вПроцедура активации компонента Service Broker), поэтому транзакция обрабатывается внешней процедурой.

Я сделал так, чтобы процедура возвращала результат (1 для успеха, 0 для сбоя) + возвращает сообщение для целей регистрации в случаеошибки.

Внутри процедуры:

  • Установить XACT_ABORT ON;- транзакция не должна быть отключена из-за триггеров.
  • Объявление @PartOfTran bit = 0;- используется для сохранения статуса: 1 - если эта процедура является частью другой транзакции или 0 - должна начинать новую транзакцию.
  • Если это является частью другого транса, тогда укажите точку сохранения.Если нет - тогда начните транзакцию.
  • Начните пробовать блокировать - делайте все, и если ошибок нет И если это не вложенная транзакция, сделайте коммит.Если это вложенная транзакция, то будет выполнена фиксация в процедуре вызывающей стороны.
  • В случае ошибки: если это вложенная транзакция и транзакция находится в состоянии фиксации, можно выполнить откат до точки сохранения «MyTran».если это не часть транзакции, транзакция отката называется «MyTran».Во всех остальных случаях - просто верните код ошибки и сообщение.

Код выглядит так:

Create Procedure dbo.usp_MyProcedure 
(
    -- params here ...
    @ReturnCode int out, -- 1 Success, != 1 Error
    @ReturnMsg nvarchar(2048) out
)
AS
Begin
    Set NoCount ON;
    Set XACT_ABORT ON; 

    Declare @PartOfTran bit = 0;

    IF(@@TRANCOUNT > 0)
        Begin 
            SET @PartOfTran = 1;
            SAVE TRAN MyTran;
        END
    Else
        BEGIN TRAN MyTran;

    Begin Try
        -- insert table1
        -- update table2
        -- ....

        IF(@PartOfTran = 0) 
            COMMIT TRAN MyTran;
        Select @ReturnCode = 1, @ReturnMsg = Null;
    End Try
    Begin Catch
        IF (XACT_STATE() = 1 And @PartOfTran = 1) OR @PartOfTran = 0
            Rollback Tran MyTran;
        Select @ReturnCode = 0, @ReturnMsg = ERROR_MESSAGE();
    End Catch
End

ДРУГАЯ ЛИТЕРАТУРА:

Из моих любимых блоггеров видели:

  1. sommarskog - но мне не нравится, что в "external_sp" есть строка "IF @@ trancount> 0 ROLLBACK TRANSACTION", потому что в моем случае внешняя процедура может быть вызвана в транзакции, поэтому в этом случае у меня есть "Transactioncount после EXECUTE указывает на несовпадающее количество операторов BEGIN и COMMIT. Предыдущий count = 1, текущий count = 0. "
  2. rusanu - фактически почти такой же, как я написал здесь (возможно, идея приходитиз этого поста в блоге я написал свое решение на основе всего, что я прочитал по этому вопросу).Этот пост в блоге до сих пор не решает, что мне делать с несокрушимыми транзакциями.Это проблема в случае Service Broker.Как я могу сделать правильную запись сообщения об ошибке, если мне нужно откатить незафиксированную транзакцию?У меня есть идеи по этому поводу, но все они кажутся обходными, а не элегантными решениями.

Ответы [ 2 ]

3 голосов
/ 06 марта 2012

Вы не сможете найти решение, которое откатывает только работу, выполненную в usp_MyProcedure при любых условиях .Рассмотрим наиболее очевидный пример: тупик.Когда вы получаете уведомление об исключении 1205 (вы были выбраны в качестве жертвы тупика), транзакция уже откатилась (чтобы разрешить прогресс).Поскольку обработка ошибок идет, единственным безопасным вариантом является дальнейшее повышение и повторный бросок, чтобы у вызывающей стороны была возможность отреагировать.«Неуправляемая транзакция» - это всего лишь вариация этой темы: просто невозможно, чтобы обработка ошибок могла восстановиться в такой ситуации таким образом, чтобы это было целесообразно для вызывающей стороны, когда вызывающая сторона начала транзакцию.Лучше всего поднять (перебросить).Вот почему я использовал шаблон, который вы видели в моем блоге по адресу Исключительная обработка и вложенные транзакции

Учитывая это в контексте Service Broker, это означает, что не существует полностью пуленепробиваемого, исключительного безопасногопроцедура обработки сообщенийЕсли вы нажмете на незафиксированную транзакцию (или транзакцию, которая уже откатилась к тому времени, когда вы обрабатываете блок перехвата, например 1205 взаимоблокировка), тогда весь пакет полученных сообщений должен будет откатиться.Регистрация обычно выполняется в таких ситуациях после самого внешнего блока перехвата (как правило, найдите активированную процедуру).Вот псевдокод того, как это будет работать:

usp_myActivatedProc
as
@commited = false;
@received = 0;
@errors = 0;

begin transaction
begin try
  receive ... into @table;
  @received = @@row_count;
  foreach message in @table
    save transaction
    begin try
       process one message: exec usp_myProcedure @msg
    end try
    begin catch
      if xact_state()=1
        rollback to savepoint
        @errors += 1;
        -- decide what to do with failed message, log
        -- this failure may still be committed (receive won't roll back yet)
      else 
        -- this is a lost cause, re-throw
        raiserror
    end catch
    fetch next @table
  endfor
  commit
  @commited = true;
end try
catch
    @error_message = error_message();
    if xact_state() != 0
      rollback
end catch
if @commited = false
begin
   insert into logging 'failed', @received, @error_message
end
0 голосов
/ 05 марта 2012
        -- insert table1
IF @@ERROR > 0
        GOTO _FAIL
        -- update table2
        -- ....
IF @@ERROR > 0
GOTO _FAIL

GOTO _SUCCESS


_ERROR:
    ROLLBACK TRAN
    SET @ReturnCode = 1
    RETURN
_FAIL:
    ROLLBACK TRAN
    SET @ReturnCode = 1
    RETURN

_SUCCESS:
    COMMIT TRAN

в конце транса вставьте

 GOTO _SUCCESS

, чтобы он зафиксировал транс, если не было ошибок.

И все ваши вставки или оператор обновления внутриTran вставляет

IF @@ERROR > 0
        GOTO _FAIL

, поэтому, когда он попадет в ошибку .. он перейдет к

   _FAIL:
        ROLLBACK TRAN
        SET @ReturnCode = 1
        RETURN

, и вы можете установить все возвращаемое значение там

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...