Как выполнить откат при запуске 3 хранимых процедур из одной хранимой процедуры

23

У меня есть хранимая процедура, которая выполняет только 3 хранимые процедуры внутри них. Я использую только 1 параметр для хранения, если мастер SP успешно.

Если первая хранимая процедура работает нормально в основной хранимой процедуре, но 2-я хранимая процедура дает сбой, то она автоматически откатит все SP в главном SP или мне нужно будет выполнить какую-то команду?

Вот моя процедура:

CREATE PROCEDURE [dbo].[spSavesomename] 
    -- Add the parameters for the stored procedure here

    @successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
    begin Try
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

   BEGIN 

   EXEC [dbo].[spNewBilling1]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling2]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling3]

   END 

   set @successful  = 1

   end Try

    begin Catch
        rollback transaction createSavesomename
        insert into dbo.tblErrorMessage(spName, errorMessage, systemDate) 
             values ('spSavesomename', ERROR_MESSAGE(), getdate())

        return
    end Catch
commit transaction createSavesomename
return
END

GO
user2483342
источник
Если spNewBilling3выдает ошибку, но вы не хотите откат spNewBilling2или spNewBilling1, то просто удалите [begin|rollback|commit] transaction createSavebillinginvoiceиз spSavesomename.
Майк

Ответы:

56

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

НО вот некоторые вещи, на которые следует обратить внимание в отношении транзакций (по крайней мере, в SQL Server):

  • Существует только одна реальная транзакция (первая), независимо от того, сколько раз вы звонитеBEGIN TRAN

    • Вы можете назвать транзакцию (как вы сделали здесь) , и это имя будет появляться в журналах, но именования имеют смысл только для первой / внешней большей сделки (потому что опять же , первых из них является сделка).
    • Каждый раз, когда вы звоните BEGIN TRAN, независимо от того, назван он или нет, счетчик транзакций увеличивается на 1.
    • Вы можете увидеть текущий уровень, выполнив SELECT @@TRANCOUNT;
    • Любые COMMITкоманды, введенные, когда значение @@TRANCOUNTравно 2 или выше, не делают ничего, кроме как уменьшают, по одному, счетчик транзакций.
    • Ничто никогда не совершается до тех пор , пока COMMITне будет выдано , когда @@TRANCOUNTнаходится в1
    • На случай, если приведенная выше информация не указывает четко: независимо от уровня транзакции, фактическое вложение транзакций отсутствует.
  • Сохранение точки позволяют создавать подмножество работы в рамках в операции , которая может быть отменена.

    • Точки сохранения создаются / помечаются с помощью SAVE TRAN {save_point_name}команды
    • Точки сохранения отмечают начало подмножества работ, которые можно отменить без отката всей транзакции.
    • Имена точек сохранения не обязательно должны быть уникальными, но использование одного и того же имени более одного раза создает отдельные точки сохранения.
    • Сохранить точки могут быть вложенными.
    • Точки сохранения не могут быть зафиксированы.
    • Сохранить точки можно отменить с помощью ROLLBACK {save_point_name}. (подробнее об этом ниже)
    • Откат точки сохранения приведет к отмене любой работы, выполненной после самого последнего вызова SAVE TRAN {save_point_name}, включая любые точки сохранения, созданные после создания отката (отсюда и «вложение»).
    • Откат точки сохранения не влияет на количество транзакций / уровень
    • Любая работа, выполненная до начала, SAVE TRANне может быть отменена, кроме как путем выдачи полной ROLLBACKтранзакции.
    • Просто чтобы прояснить: выдача COMMITкогда @@TRANCOUNTравен 2 или выше, не влияет на точки сохранения (потому что уровни транзакций выше 1 не существуют вне этого счетчика).
  • Вы не можете совершать определенные именованные транзакции. «Имя» транзакции, если оно предоставлено вместе с COMMIT, игнорируется и существует только для удобства чтения.

  • ROLLBACKВыдается без имени всегда будет откатить все транзакции.

  • ROLLBACKВыдается имя должно соответствовать либо:

    • Первая транзакция, при условии, что она была названа:
      если не SAVE TRANбыло вызвано ни одного имени с таким же именем транзакции, это откатит ВСЕ транзакции.
    • «Точка сохранения» (описанная выше):
      это поведение «отменяет» все изменения, сделанные с момента вызова самой последней версии SAVE TRAN {save_point_name}.
    • Если первой транзакции было присвоено имя a) и b), для которой были SAVE TRANвыданы команды с ее именем, то каждый ROLLBACK этого имени транзакции отменяет каждую точку сохранения, пока от этого имени не останется ни одной. После этого ROLLBACK, выпущенный с таким именем, откатит ВСЕ транзакции.
    • Например, предположим, что следующие команды были запущены в указанном порядке:

      BEGIN TRAN A -- @@TRANCOUNT is now 1
      -- DML Query 1
      SAVE TRAN A
      -- DML Query 2
      SAVE TRAN A
      -- DML Query 3
      
      BEGIN TRAN B -- @@TRANCOUNT is now 2
      SAVE TRAN B
      -- DML Query 4

      Теперь, если вы выдадите запрос (каждый из следующих сценариев не зависит друг от друга):

      • ROLLBACK TRAN Bодин раз: это отменит "DML Query 4". @@TRANCOUNTеще 2.
      • ROLLBACK TRAN Bдважды: отменяется «DML Query 4», а затем происходит ошибка, поскольку для «B» нет соответствующей точки сохранения. @@TRANCOUNTеще 2.
      • ROLLBACK TRAN Aодин раз: он отменяет «DML Query 4» и «DML Query 3». @@TRANCOUNTеще 2.
      • ROLLBACK TRAN Aдважды: отменит «DML Query 4», «DML Query 3» и «DML Query 2». @@TRANCOUNTеще 2.
      • ROLLBACK TRAN Aтрижды: он отменяет «DML Query 4», «DML Query 3» и «DML Query 2». Затем он откатит всю транзакцию (все, что осталось, это «DML Query 1»). @@TRANCOUNTсейчас 0.
      • COMMITодин раз: @@TRANCOUNTснижается до 1.
      • COMMITодин раз, а затем ROLLBACK TRAN Bодин раз: @@TRANCOUNTуменьшается до 1. Затем он отменяет «DML Query 4» (доказывая, что COMMIT ничего не делал). @@TRANCOUNTвсе еще 1.
  • Имена транзакций и имена точек сохранения:

    • может содержать до 32 символов
    • обрабатываются как имеющие двоичное сопоставление (без учета регистра, как указано в документации), независимо от сопоставлений на уровне экземпляра или на уровне базы данных.
    • Подробности смотрите в разделе « Имена транзакций » следующего поста: « Что в имени ?: Внутри странного мира идентификаторов T-SQL»
  • Хранимая процедура сама по себе не является неявной транзакцией. Каждый запрос, если явная транзакция не была запущена, является неявной транзакцией. Вот почему явные транзакции вокруг отдельных запросов не нужны, если только для этого не может быть программной причины, в ROLLBACKпротивном случае любая ошибка в запросе является автоматическим откатом этого запроса.

  • При вызове хранимой процедуры она должна завершиться со значением @@TRANCOUNT, совпадающим с тем, когда она была вызвана. Это означает, что вы не можете:

    • Запустите BEGIN TRANв proc без фиксации, ожидая фиксации в вызывающем / родительском процессе.
    • Вы не можете выдать, ROLLBACKесли явная транзакция была запущена до вызова процедуры, так как она вернется @@TRANCOUNTк 0.

    Если вы выйдете из хранимой процедуры с количеством транзакций, которое будет выше или ниже, чем при просмотре, вы получите ошибку, похожую на:

    Сообщение 266, Уровень 16, Состояние 2, Процедура YourProcName, Строка 0
    Количество транзакций после EXECUTE указывает на несовпадающее количество операторов BEGIN и COMMIT. Предыдущий счет = X, текущий счет = Y.

  • Табличные переменные, как и обычные переменные, не связаны транзакциями.


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

То, как я справляюсь с этим уже несколько лет, похоже, работает хорошо, только BEGIN/ COMMIT/ ROLLBACKна самом внешнем слое. Вызовы sub-proc просто пропускают команды транзакции. Ниже я изложил, что я вкладываю в каждый процесс (ну, каждый, который нуждается в обработке транзакций).

  • В верхней части каждого процесса, DECLARE @InNestedTransaction BIT;
  • Вместо простого BEGIN TRANсделайте:

    IF (@@TRANCOUNT = 0)
    BEGIN
       SET @InNestedTransaction = 0;
       BEGIN TRAN; -- only start a transaction if not already in one
    END;
    ELSE
    BEGIN
       SET @InNestedTransaction = 1;
    END;
  • Вместо простого COMMITсделайте:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
  • Вместо простого ROLLBACKсделайте:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       ROLLBACK;
    END;

Этот метод должен работать одинаково независимо от того, была ли транзакция запущена в SQL Server или запущена на уровне приложения.

Для полного шаблона этой обработки транзакций в TRY...CATCHконструкции, пожалуйста, смотрите мой ответ на следующий вопрос DBA.SE: Требуем ли мы обрабатывать транзакции в C # Code, а также в хранимых процедурах .


Выходя за рамки «основ», необходимо учитывать некоторые дополнительные нюансы транзакций:

  • По умолчанию транзакции в большинстве случаев автоматически не откатываются / отменяются при возникновении ошибки. Обычно это не проблема, если вы правильно обрабатываете ошибки и звоните ROLLBACKсами. Однако иногда все усложняется, например, в случае ошибок пакетного прерывания или при использовании OPENQUERY(или связанных серверов в целом), и в удаленной системе возникает ошибка. Хотя большинство ошибок можно отследить с помощью TRY...CATCH, есть две, которые не могут быть перехвачены таким образом (хотя не могу вспомнить, какие из них на данный момент - исследование). В этих случаях вы должны использовать SET XACT_ABORT ONдля правильного отката транзакции.

    SET XACT_ABORT ON заставляет SQL Server , чтобы немедленно откат любой сделки (если он активен) и преждевременное прекращение партии , если любая ошибка происходит. Этот параметр существовал до SQL Server 2005, в котором была представлена TRY...CATCHконструкция. По большей части, TRY...CATCHобрабатывает большинство ситуаций и поэтому в основном устраняет необходимость XACT_ABORT ON. Тем не менее, при использовании OPENQUERY(и, возможно, еще один сценарий, который я не могу вспомнить в данный момент), вам все равно придется использовать SET XACT_ABORT ON;.

  • Внутри Trigger XACT_ABORTнеявно установлено значение ON. Это вызывает любую ошибку в Trigger, чтобы отменить весь оператор DML, который запустил Trigger.

  • У вас всегда должна быть правильная обработка ошибок, особенно при использовании транзакций. TRY...CATCHКонструкт, введенный в SQL Server 2005 предоставляет средства обработки почти во всех ситуациях улучшения приветственного по тестированию для @@ERRORпосле каждого заявления, которое не помогло много с партиями прерывания ошибок.

    TRY...CATCHпредставил новое "государство", однако. Если конструкция не используется TRY...CATCH, если у вас есть активная заявка и возникает ошибка, существует несколько путей:

    • XACT_ABORT OFFи ошибка прерывания оператора: транзакция все еще активна, и обработка продолжается со следующего оператора , если таковой имеется.
    • XACT_ABORT OFFи большинство ошибок при пакетном прерывании: транзакция все еще активна, и обработка продолжается со следующего пакета , если он есть.
    • XACT_ABORT OFFи некоторые ошибки прерывания пакета: откат транзакции и обработка продолжается со следующего пакета , если он есть.
    • XACT_ABORT ONи любая ошибка: транзакция откатывается, и обработка продолжается со следующей партии , если таковая имеется.


    ОДНАКО, при использовании TRY...CATCHошибки прерывания пакета не отменяют пакет, а передают управление CATCHблоку. Когда XACT_ABORTэто OFF, транзакция будет по- прежнему активны подавляющее большинство времени, и вы должны COMMIT, или , скорее всего, ROLLBACK. Но при обнаружении определенных ошибок пакетного прерывания (например, с OPENQUERY) или когда XACT_ABORTесть ON, Транзакция будет в новом состоянии, «uncommitable». В этом состоянии вы не можете и не COMMITможете выполнять какие-либо операции DML. Все, что вы можете сделать, это ROLLBACKи SELECTзаявления. Однако в этом «неудобном» состоянии транзакция была откатана после возникновения ошибки, и выдача - ROLLBACKэто просто формальность, но она должна быть выполнена.

    Функция XACT_STATE может использоваться для определения, активна ли транзакция, не отправлена ​​или не существует. Рекомендуется (по крайней мере, для некоторых) проверить эту функцию в CATCHблоке, чтобы определить, является ли результат -1(то есть недоступным) вместо проверки, если @@TRANCOUNT > 0. Но с XACT_ABORT ONэтим должно быть единственно возможное состояние, поэтому кажется, что тестирование @@TRANCOUNT > 0и XACT_STATE() <> 0эквивалентны. С другой стороны, когда XACT_ABORTесть OFFи есть активная заявка, то возможно иметь состояние одного 1или -1в CATCHблоке, что допускает возможность выдачи COMMITвместо ROLLBACK(хотя, я не могу вспомнить случай, когда кто-то хотел быCOMMITесли Сделка является обязательной). Дополнительную информацию и исследования по использованию XACT_STATE()в CATCHблоке с XACT_ABORT ONможно найти в моем ответе на следующий вопрос DBA.SE: В каких случаях транзакция может быть зафиксирована изнутри блока CATCH, когда для XACT_ABORT установлено значение ON? , Обратите внимание, что существует небольшая ошибка, XACT_STATE()которая приводит к ложному возвращению 1в некоторых сценариях: XACT_STATE () возвращает 1, когда используется в SELECT с некоторыми системными переменными, но без предложения FROM


Примечания об оригинальном коде:

  • Вы можете удалить имя, данное транзакции, поскольку оно не помогает никому.
  • Вам не нужно BEGINи ENDвокруг каждого EXECзвонка
Соломон Руцкий
источник
2
Это действительно хороший, хороший ответ.
МакНетс
1
Вау, это один исчерпывающий ответ! Спасибо! Кстати, на следующей странице указаны ошибки, которые вы намекаете на то, что они не попадают в поле «Попытаться ... поймать?». (Под заголовком «Ошибки, не затронутые TRY… CATCH Construct»? Technet.microsoft.com/en-us/library/ms175976(v=sql.110).aspx
jrdevdba,
1
@jrdevdba Спасибо :-). И тебе добро пожаловать. Что касается ошибок, которые не попали в ловушку, я в значительной степени имел в виду эти два: Compile errors, such as syntax errors, that prevent a batch from runningи Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.. Но они случаются не очень часто, и когда вы обнаружите такую ​​ситуацию, либо исправьте ее (если это ошибка в коде), либо поместите ее в подпроцесс ( EXECили sp_executesql), чтобы TRY...CATCHможно было ее перехватить.
Соломон Руцкий
2

Да, если из-за какого-либо кода отката ошибки в операторе catch вашей главной хранимой процедуры будет выполнено выполнение, он откатит все операции, выполненные любым прямым оператором или через любую из ваших вложенных хранимых процедур в нем.

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

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

Для справки http://technet.microsoft.com/en-us/library/ms189336(v=sql.105).aspx

aasim.abdullah
источник