Вложенные хранимые процедуры, содержащие шаблон отката TRY CATCH?


меня интересуют побочные эффекты и потенциальные проблемы следующим образом:

CREATE PROCEDURE [Name]
AS
BEGIN
    BEGIN TRANSACTION
    BEGIN TRY
        [...Perform work, call nested procedures...]
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION
        RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
    END CATCH
END

насколько я понимаю, этот шаблон звучит при использовании с одной процедурой-процедура либо завершит все свои утверждения без ошибок, либо откатит все действия и сообщит об ошибке.

однако, когда одна хранимая процедура вызывает другую хранимую процедуру, чтобы выполнить некоторую подблоку работы (с пониманием того, что меньшая процедура иногда вызывается сама по себе) я вижу проблему, возникающую в связи с откатами - выдается информационное сообщение (Уровень 16) с указанием The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.. Это я предполагаю, потому что откат в подпроцедуре всегда откатывает самую внешнюю транзакцию, а не только транзакцию, запущенную в подпроцедуре.

Я хочу, чтобы все это откатилось и прервалось, если произойдет какая-либо ошибка (и ошибка, сообщенная клиенту как ошибка SQL), я просто не уверен во всем побочные эффекты, которые исходят от внешних слоев, пытающихся откатить транзакцию,которая уже была откатана. Возможно, проверка @@TRANCOUNT перед выполнением отката на каждом уровне try CATCH?

наконец, есть клиентский конец (Linq2SQL), который имеет свой собственный уровень транзакций:

try
{
    var context = new MyDataContext();
    using (var transaction = new TransactionScope())
    {       
            // Some Linq stuff
        context.SubmitChanges();
        context.MyStoredProcedure();
        transactionComplete();
    }
}
catch
{
    // An error occured!
}

в случае, если хранимая процедура, "MySubProcedure", называется внутри MyStoredProcedure вызывает ошибку, могу ли я быть уверен, что все ранее сделанное в Mystoredprocedure будет откат, все операции Linq, выполненные SubmitChanges, будут откатаны, и, наконец, что ошибка будет зарегистрирована? Или что мне нужно изменить в моем шаблоне, чтобы обеспечить атомарность всей операции, при этом позволяя использовать дочерние части по отдельности (т. е. подпроцедуры все равно должны иметь одинаковую атомную защиту)

5 52

5 ответов:

это наш шаблон (журнал ошибок удален)

это предназначено для обработки

объяснениями:

  • все TXN begin и commit/rollbacks должны быть сопряжены так, чтобы @@TRANCOUNT то же самое при входе и выход

  • несоответствий @@TRANCOUNT вызвать ошибку 266, потому что

    • BEGIN TRAN С шагом @@TRANCOUNT

    • COMMIT уменьшается @@TRANCOUNT

    • ROLLBACK возвращает @@TRANCOUNT к нулю

  • вы не можете уменьшить @@TRANCOUNT для текущей области
    Это то, что вы думаете, является " внутренним сделка"

  • SET XACT_ABORT ON подавляет ошибку 266, вызванную несоответствием @@TRANCOUNT
    А также занимается такими вопросами, как это "тайм-аут транзакции SQL Server" ВКЛ dba.se

  • это позволяет для TXNs стороны клиента (как LINQ) Одна хранимая процедура может быть частью распределенной или XA-транзакции или просто инициирована в клиентском коде (например, .net TransactionScope)

использование:

  • каждый сохраненный процесс должен соответствовать одному и тому же шаблону

резюме

  • так что не создавайте больше TXNs, чем вам нужно

код

CREATE PROCEDURE [Name]
AS
SET XACT_ABORT, NOCOUNT ON

DECLARE @starttrancount int

BEGIN TRY
    SELECT @starttrancount = @@TRANCOUNT

    IF @starttrancount = 0
        BEGIN TRANSACTION

       [...Perform work, call nested procedures...]

    IF @starttrancount = 0 
        COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 AND @starttrancount = 0 
        ROLLBACK TRANSACTION;
    THROW;
    --before SQL Server 2012 use 
    --RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
GO

Примечания:

  • проверка отката на самом деле избыточна из-за SET XACT_ABORT ON. Тем не менее, это заставляет меня чувствовать себя лучше, выглядит странно без, и позволяет в ситуациях, когда вы не хотите его на

  • Ремус Rusanu есть похожие раковины который использует точки сохранения. Я предпочитаю атомарный вызов DB и не использую частичные обновления, такие как их статья

Я не парень Linq (и не Эрланд), но он написал абсолютные Библии по обработке ошибок. Вне осложнений Linq может добавить к вашей проблеме, все ваши другие вопросы должны быть даны ответы здесь:

http://www.sommarskog.se/error_handling/Part1.html

(старая ссылка: http://www.sommarskog.se/error_handling_2005.html)

чтобы решить проблему возврата номера ошибки и номера строки, упомянутого @AlexKuznetsov, можно поднять ошибку как таковую:

DECLARE @ErrorMessage NVARCHAR(4000)
DECLARE @ErrorSeverity INT
DECLARE @ErrorState INT
DECLARE @ErrorLine INT
DECLARE @ErrorNumber INT

SELECT @ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE(),
@ErrorNumber = ERROR_NUMBER(),
@ErrorLine = ERROR_LINE()

RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine)

-- @Аманда метод выше не возвращает правильный номер ошибки

DECLARE  
  @ErrorMessage   nvarchar(4000),  
  @ErrorSeverity   int,  
  @ErrorState int,  
  @ErrorLine  int,  
  @ErrorNumber   int  

BEGIN TRY  
 SELECT 1/0; -- CATCH me  
END TRY  

BEGIN CATCH  

  DECLARE @err int = @@ERROR  

  PRINT @err           -- 8134, divide by zero  
  PRINT ERROR_NUMBER() -- 8134  

  SELECT  
    @ErrorMessage  = ERROR_MESSAGE(),  
    @ErrorSeverity = ERROR_SEVERITY(),  
    @ErrorState    = ERROR_STATE(),  
    @ErrorNumber   = ERROR_NUMBER(),  
    @ErrorLine     = ERROR_LINE()  

  -- error number = 50000 :(  
  RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, @ErrorLine)  

END CATCH  

-- error number = 8134  
SELECT 1/0

в случае, если в CATCH не требуется специальная обработка ошибок, кроме retrow и сохраненной цепочки вызовов procs, не слишком длинная, может быть целесообразно использовать такой простой шаблон:

create procedure someNestedSP
as
SET XACT_ABORT ON
begin transaction
-- do some work or call some other similar SP
commit transaction

Он также откатывает корневую транзакцию со всеми "вложенными" в случае любой ошибки, но код короче и проще, чем решение @gbn. И все же XACT_ABORT заботится о большинстве вопросов, упомянутых там.

могут быть дополнительные накладные расходы для вложенности транзакций, но это может быть не так наверное, слишком высоко.