Como reverter quando 3 procedimentos armazenados são iniciados em um procedimento armazenado

23

Eu tenho um procedimento armazenado que executa apenas 3 procedimentos armazenados dentro deles. Estou usando apenas 1 parâmetro para armazenar se o SP mestre for bem-sucedido.

Se o primeiro procedimento armazenado funcionar bem no procedimento mestre armazenado, mas o segundo procedimento armazenado falhar, ele reverterá automaticamente todos os SPs no SP mestre ou preciso executar algum comando?

Aqui está o meu procedimento:

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
fonte
Se spNewBilling3gera um erro, mas você não deseja reverter spNewBilling2ou spNewBilling1, basta remover [begin|rollback|commit] transaction createSavebillinginvoicede spSavesomename.
Mike

Respostas:

56

Dado apenas o código mostrado na pergunta e supondo que nenhum dos três subprocessos tenha manipulação de transação explícita, sim, um erro em qualquer um dos três subprocessos será capturado e ROLLBACKo CATCHbloco reverterá tudo do trabalho.

MAS, aqui estão algumas coisas a serem observadas sobre transações (pelo menos no SQL Server):

  • Existe apenas uma transação real (a primeira), não importa quantas vezes você ligueBEGIN TRAN

    • Você pode nomear uma transação (como fez aqui) e esse nome aparecerá nos logs, mas nomear somente tem significado para a primeira / mais externa transação (porque, novamente, a primeira é a transação).
    • Cada vez que você ligou BEGIN TRAN, independentemente de seu nome, o contador de transações é incrementado em 1.
    • Você pode ver o nível atual fazendo SELECT @@TRANCOUNT;
    • Qualquer COMMITcomando emitido quando @@TRANCOUNTestá em 2 ou acima não faz nada além de reduzir, um de cada vez, o contador de transações.
    • Nada está comprometida até que um COMMITé emitido quando o @@TRANCOUNTé em1
    • Caso as informações acima não indiquem claramente: independentemente do nível da transação, não há aninhamento real de transações.
  • Os pontos de economia permitem criar um subconjunto de trabalho dentro da transação que pode ser desfeito.

    • Salvar pontos são criados / marcados através do SAVE TRAN {save_point_name}comando
    • Salvar pontos marcam o início do subconjunto de trabalho que pode ser desfeito sem reverter toda a transação.
    • Os nomes dos pontos de salvamento não precisam ser exclusivos, mas o uso do mesmo nome mais de uma vez ainda cria pontos de salvamento distintos.
    • Salvar pontos podem ser aninhados.
    • Salvar pontos não podem ser confirmados.
    • Os pontos de economia podem ser desfeitos via ROLLBACK {save_point_name}. (mais sobre isso abaixo)
    • A reversão de um ponto de salvamento irá desfazer qualquer trabalho que tenha ocorrido após a chamada mais recenteSAVE TRAN {save_point_name} , incluindo os pontos de salvamento criados após a criação do retrocesso (daí o "aninhamento").
    • A reversão de um ponto de salvamento não afeta a contagem / nível da transação
    • Qualquer trabalho realizado antes da inicial SAVE TRANnão pode ser desfeito, exceto pela emissão de uma ROLLBACKtransação completa.
    • Só para esclarecer: emitir um COMMITquando @@TRANCOUNTé igual a 2 ou superior, não afeta os pontos de salvamento (porque, novamente, os níveis de transação acima de 1 não existem fora desse contador).
  • Você não pode confirmar transações nomeadas específicas. A transação "nome", se fornecida junto com o COMMIT, é ignorada e existe apenas para facilitar a leitura.

  • Um ROLLBACKemitido sem nome sempre reverterá TODAS as transações.

  • Um ROLLBACKemitido com um nome deve corresponder a:

    • A primeira transação, assumindo o nome:
      Supondo que não SAVE TRANtenha sido chamado com o mesmo nome de transação, isso reverterá TODAS as transações.
    • Um "ponto de salvamento" (descrito acima):
      esse comportamento "desfaz" todas as alterações feitas desde que a mais recente SAVE TRAN {save_point_name} foi chamada.
    • Se a primeira transação foi a) nomeada eb) teve SAVE TRANcomandos emitidos com seu nome, cada ROLLBACK desse nome de transação desfará cada ponto de salvamento até que não haja mais esse nome. Depois disso, um ROLLBACK emitido com esse nome reverterá TODAS as transações.
    • Por exemplo, suponha que os seguintes comandos foram executados na ordem mostrada:

      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

      Agora, se você emitir (cada um dos seguintes cenários é independente um do outro):

      • ROLLBACK TRAN Buma vez: desfaz a "Consulta 4 da DML". @@TRANCOUNTainda é 2.
      • ROLLBACK TRAN Bduas vezes: desfaz a "Consulta DML 4" e erro, pois não há um ponto de salvamento correspondente para "B". @@TRANCOUNTainda é 2.
      • ROLLBACK TRAN Auma vez: desfará "DML Query 4" e "DML Query 3". @@TRANCOUNTainda é 2.
      • ROLLBACK TRAN Aduas vezes: desfará "DML Query 4", "DML Query 3" e "DML Query 2". @@TRANCOUNTainda é 2.
      • ROLLBACK TRAN Atrês vezes: desfaz "Consulta DML 4", "Consulta DML 3" e "Consulta DML 2". Em seguida, ele reverterá a transação inteira (tudo o que restou foi "Consulta DML 1"). @@TRANCOUNTagora é 0.
      • COMMITuma vez: @@TRANCOUNTdesce para 1.
      • COMMITuma vez e depois ROLLBACK TRAN Buma vez: @@TRANCOUNTdesce para 1. Em seguida, desfaz a "Consulta DML 4" (provando que o COMMIT não fez nada). @@TRANCOUNTainda é 1.
  • Nomes de transações e nomes de pontos de salvamento:

    • pode ter até 32 caracteres
    • são tratados como tendo um agrupamento binário (sem distinção entre maiúsculas e minúsculas, conforme a documentação atualmente), independentemente dos agrupamentos no nível da instância ou no banco de dados.
    • Para obter detalhes, consulte a seção Nomes de transações da seguinte postagem: O que há em um nome ?: Dentro do mundo maluco dos identificadores T-SQL
  • Um procedimento armazenado não é, por si só, uma transação implícita. Cada consulta, se nenhuma transação explícita foi iniciada, é uma transação implícita. É por isso que transações explícitas em torno de consultas únicas não são necessárias, a menos que possa haver um motivo programático para fazer isso ROLLBACK; caso contrário, qualquer erro na consulta é uma reversão automática dessa consulta.

  • Ao chamar um procedimento armazenado, ele deve sair com o valor de @@TRANCOUNTser o mesmo de quando foi chamado. Ou seja, você não pode:

    • Inicie um BEGIN TRANno processo sem confirmar, esperando confirmar no processo de chamada / pai.
    • Você não pode emitir a ROLLBACKse uma transação explícita tiver sido iniciada antes do processo ser chamado, pois retornará @@TRANCOUNTa 0.

    Se você sair de um procedimento armazenado com uma contagem de transações maior ou menor do que quando ele começou, você receberá um erro semelhante a:

    Msg 266, Nível 16, Estado 2, Procedimento YourProcName, Linha 0
    A contagem de transações após EXECUTE indica um número incompatível de instruções BEGIN e COMMIT. Contagem anterior = X, contagem atual = Y.

  • Variáveis ​​de tabela, assim como variáveis ​​regulares, não são vinculadas por transações.


Em relação à manipulação de transações em procs que podem ser chamados de forma independente (e, portanto, precisam de manipulação de transações) ou chamadas de outros procs (portanto, não precisam de manipulação de transações): isso pode ser realizado de duas maneiras diferentes.

A maneira que eu tenho lidado com isso há vários anos, agora que parece funcionar bem, é apenas BEGIN/ COMMIT/ ROLLBACKna camada mais externa. Chamadas de subprocesso apenas ignoram os comandos de transação. Descrevi abaixo o que coloco em cada processo (bem, cada um que precisa de manipulação de transações).

  • No topo de cada proc, DECLARE @InNestedTransaction BIT;
  • Em vez de simples BEGIN TRAN, faça:

    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;
  • Em vez de simples COMMIT, faça:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
  • Em vez de simples ROLLBACK, faça:

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

Esse método deve funcionar da mesma maneira, independentemente de a transação ter sido iniciada no SQL Server ou se foi iniciada na camada de aplicativo.

Para obter o modelo completo desta manipulação de transações na TRY...CATCHconstrução, consulte minha resposta para a seguinte pergunta do DBA.SE: Somos obrigados a manipular transações no código C # e no procedimento armazenado .


Indo além do "básico", existem algumas nuances adicionais de transações a serem observadas:

  • Por padrão, as Transações, na maioria das vezes, não são revertidas / canceladas automaticamente quando ocorre um erro. Isso geralmente não é um problema, desde que você tenha um tratamento de erros adequado e ligue para ROLLBACKsi mesmo. No entanto, às vezes as coisas ficam complicadas, como no caso de erros de interrupção de lote ou ao usar OPENQUERY(ou Servidores Vinculados em geral) e ocorre um erro no sistema remoto. Embora a maioria dos erros possa ser interceptada TRY...CATCH, existem dois que não podem ser interceptados dessa maneira (embora não se lembre de quais no momento - pesquisando). Nesses casos, você deve usar SET XACT_ABORT ONpara reverter corretamente a transação.

    SET XACT_ABORT ON faz o SQL Server reverter imediatamente qualquer transação (se uma estiver ativa) e abortar o lote se ocorrer algum erro. Essa configuração existia antes do SQL Server 2005, que introduziu a TRY...CATCHconstrução. Na maioria das vezes, TRY...CATCHlida com a maioria das situações e, portanto, obsoleta a necessidade XACT_ABORT ON. No entanto, ao usar OPENQUERY(e possivelmente um outro cenário que não me lembro no momento), você ainda precisará usá-lo SET XACT_ABORT ON;.

  • Dentro de um gatilho, XACT_ABORTestá implicitamente definido como ON. Isso causa qualquer erro no gatilho para cancelar a instrução DML inteira que acionou o gatilho.

  • Você sempre deve ter o tratamento de erros adequado, especialmente ao usar Transações. A TRY...CATCHconstrução, introduzida no SQL Server 2005, fornece um meio de lidar com quase todas as situações, uma melhoria bem-vinda sobre o teste @@ERRORapós cada instrução, o que não ajudou muito com erros de interrupção de lote.

    TRY...CATCHintroduziu um novo "estado", no entanto. Quando nãoTRY...CATCH estiver usando a construção, se você tiver uma transação ativa e ocorrer um erro, existem vários caminhos que podem ser seguidos:

    • XACT_ABORT OFFe erro de cancelamento de instrução: a transação ainda está ativa e o processamento continua com a próxima instrução , se houver.
    • XACT_ABORT OFFe a maioria dos erros de interrupção de lote: a transação ainda está ativa e o processamento continua com o próximo lote , se houver.
    • XACT_ABORT OFFe certos erros de interrupção de lote: a transação é revertida e o processamento continua com o próximo lote , se houver.
    • XACT_ABORT ONe qualquer erro: a transação é revertida e o processamento continua com o próximo lote , se houver.


    NO ENTANTO, ao usar TRY...CATCH, os erros de interrupção de lote não abortam o lote, mas transferem o controle para o CATCHbloco. Quando XACT_ABORTfor OFF, a Transação ainda estará ativa na grande maioria das vezes, e você precisará COMMIT, ou provavelmente ROLLBACK. Porém, ao encontrar certos erros de interrupção de lote (como com OPENQUERY), ou quando XACT_ABORTestiver ON, a Transação estará em um novo estado, "não comprometível". Nesse estado, você não pode COMMIT, nem pode executar operações DML. Tudo o que você pode fazer é ROLLBACKe SELECTdeclarações. No entanto, nesse estado "incompatível", a Transação foi revertida após o erro que ocorreu e emitir a ROLLBACKé apenas uma formalidade, mas que deve ser executada.

    Uma função, XACT_STATE , pode ser usada para determinar se uma transação está ativa, incompatível ou não existe. Recomenda-se (por alguns, pelo menos) verificar esta função no CATCHbloco para determinar se o resultado é -1(ou seja, incompatível) em vez de testar se @@TRANCOUNT > 0. Mas com XACT_ABORT ON, esse deve ser o único estado possível, portanto parece que testar @@TRANCOUNT > 0e XACT_STATE() <> 0é equivalente. Por outro lado, quando XACT_ABORTexiste OFFe existe uma transação ativa, é possível ter um estado de um 1ou -1de um CATCHbloco, o que permite a possibilidade de emitir em COMMITvez de ROLLBACK(embora, não consigo pensar em um caso para quando alguém gostaria deCOMMITse a transação for confirmada). Mais informações e pesquisas sobre o uso XACT_STATE()em um CATCHbloco XACT_ABORT ONpodem ser encontradas na minha resposta à seguinte pergunta do DBA.SE: Em que casos uma transação pode ser confirmada de dentro do bloco CATCH quando XACT_ABORT está definido como ON? . Observe que há um pequeno erro XACT_STATE()que faz com que ele retorne falsamente 1em certos cenários: XACT_STATE () retorna 1 quando usado em SELECT com algumas variáveis ​​do sistema, mas sem a cláusula FROM


Notas sobre o código original:

  • Você pode remover o nome dado à transação, pois isso não ajuda em nada.
  • Você não precisa de BEGINe em ENDtorno de cada EXECchamada
Solomon Rutzky
fonte
2
É uma resposta muito boa, boa.
25417 McNets
1
Uau, essa é uma resposta abrangente! Obrigado! Aliás, a página a seguir aborda os erros que você alude que não são capturados pelo Try ... Catch? (Sob o título "Erros afetado por uma construção try ... catch"? Technet.microsoft.com/en-us/library/ms175976(v=sql.110).aspx
jrdevdba
1
@jrdevdba Thanks :-). E de nada. Em relação aos erros não capturados, eu praticamente quis dizer esses dois: Compile errors, such as syntax errors, that prevent a batch from runninge Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.. Mas eles não acontecem com muita frequência e, quando você encontrar uma situação dessas, corrija-a (se houver um erro no código) ou coloque-a em um subprocesso ( EXECou sp_executesql) para que TRY...CATCHpossa prendê-la.
Solomon Rutzky,
2

Sim, se devido a algum código de reversão de erro na instrução catch do seu procedimento armazenado principal for executado, ele reverterá todas as operações executadas por qualquer instrução direta ou através de qualquer um dos seus procedimentos armazenados aninhados.

Mesmo que você não tenha aplicado nenhuma transação explícita nos procedimentos armazenados aninhados, esse procedimento armazenado usará a transação implícita e será confirmado na conclusão, MAS você confirmou por meio de transação explícita ou implícita nos procedimentos armazenados aninhados o mecanismo do SQL Server o ignorará e irá reverter todas as ações desses procedimentos armazenados aninhados se o procedimento armazenado principal falhar e a transação for suportada por rollback.

Toda vez que a transação é confirmada ou revertida com base na ação executada no final da transação mais externa. Se a transação externa for confirmada, as transações internas aninhadas também serão confirmadas. Se a transação externa for revertida, todas as transações internas também serão revertidas, independentemente de as transações internas terem sido confirmadas ou não individualmente.

Para referência http://technet.microsoft.com/en-us/library/ms189336(v=sql.105).aspx

aasim.abdullah
fonte