Em que casos uma transação pode ser confirmada de dentro do bloco CATCH quando XACT_ABORT está definido como ON?

13

Eu tenho lido o MSDN sobre TRY...CATCHe XACT_STATE.

Ele tem o exemplo a seguir que usa XACT_STATEno CATCHbloco de uma TRY…CATCHconstrução para determinar se deve confirmar ou reverter uma transação:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Test XACT_STATE for 0, 1, or -1.
    -- If 1, the transaction is committable.
    -- If -1, the transaction is uncommittable and should 
    --     be rolled back.
    -- XACT_STATE = 0 means there is no transaction and
    --     a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT 'The transaction is in an uncommittable state.' +
              ' Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT 'The transaction is committable.' + 
              ' Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO

O que eu não entendo é: por que devo me importar e verificar o que XACT_STATEretorna?

Observe que o sinalizador XACT_ABORTestá definido ONno exemplo.

Se houver um erro grave o suficiente dentro do TRYbloco, o controle passará para CATCH. Então, se eu estou dentro da CATCH, sei que a transação teve um problema e realmente a única coisa sensata a fazer nesse caso é reverter isso, não é?

Mas, este exemplo do MSDN implica que pode haver casos em que o controle é passado CATCHe ainda faz sentido confirmar a transação. Alguém poderia dar um exemplo prático quando isso pode acontecer, quando faz sentido?

Não vejo em que casos o controle pode ser passado para dentro de CATCHuma transação que pode ser confirmada quando XACT_ABORTdefinida comoON .

O artigo do MSDN sobre SET XACT_ABORTtem um exemplo quando algumas instruções dentro de uma transação são executadas com êxito e outras falham quando XACT_ABORTé definido como OFF, eu entendo isso. Mas, SET XACT_ABORT ONcomo pode acontecer que XACT_STATE()retorne 1 dentro do CATCHbloco?

Inicialmente, eu teria escrito este código assim:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    PRINT 'Rolling back transaction.';
    ROLLBACK TRANSACTION;
END CATCH;
GO

Levando em conta uma resposta de Max Vernon, eu escreveria o código assim. Ele mostrou que faz sentido verificar se há uma transação ativa antes de tentar ROLLBACK. Ainda assim, SET XACT_ABORT ONo CATCHbloco pode ter transações condenadas ou nenhuma transação. Então, em qualquer caso, não há nada a fazer COMMIT. Estou errado?

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    IF (XACT_STATE()) <> 0
    BEGIN
        -- There is still an active transaction that should be rolled back
        PRINT 'Rolling back transaction.';
        ROLLBACK TRANSACTION;
    END;

END CATCH;
GO
Vladimir Baranov
fonte

Respostas:

8

Acontece que a transação não pode ser confirmada de dentro do CATCHbloco se XACT_ABORTestiver definida como ON.

O exemplo do MSDN é um pouco enganador, porque a verificação implica que XACT_STATEpode retornar 1 em alguns casos e pode ser possível para COMMITa transação.

IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;

Não é verdade, XACT_STATEnunca retornará 1 CATCHbloco interno se XACT_ABORTestiver definido como ON.

Parece que o código de exemplo do MSDN foi usado para ilustrar principalmente o uso da XACT_STATE()função, independentemente da XACT_ABORTconfiguração. O código de amostra parece genérico o suficiente para trabalhar com ambos XACT_ABORTdefinidos como ONe OFF. Só que com XACT_ABORT = ONa verificação IF (XACT_STATE()) = 1se torna desnecessário.


Há um conjunto muito bom de artigos detalhados sobre o tratamento de erros e transações no SQL Server por Erland Sommarskog. Na Parte 2 - Classificação de erros, ele apresenta uma tabela abrangente que reúne todas as classes de erros e como eles são tratados pelo SQL Server e como TRY ... CATCHe XACT_ABORTaltera o comportamento.

+-----------------------------+---------------------------++------------------------------+
|                             |     Without TRY-CATCH     ||        With TRY-CATCH        |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
|              SET XACT_ABORT |  OFF  |  ON   | OFF | ON  ||    ON or OFF     | OFF | ON  |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Class Name                  |    Aborts     |   Rolls   ||    Catchable     |   Dooms   |
|                             |               |   Back    ||                  |transaction|
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Fatal errors                |  Connection   |    Yes    ||       No         |    n/a    |
| Batch-aborting              |     Batch     |    Yes    ||       Yes        |    Yes    |
| Batch-only aborting         |     Batch     | No  | Yes ||       Yes        | No  | Yes |
| Statement-terminating       | Stmnt | Batch | No  | Yes ||       Yes        | No  | Yes |
| Terminates nothing at all   |    Nothing    |    No     ||       Yes        | No  | Yes |
| Compilation: syntax errors  |  (Statement)  |    No     ||       Yes        | No  | Yes |
| Compilation: binding errors | Scope | Batch | No  | Yes || Outer scope only | No  | Yes |
| Compilation: optimisation   |     Batch     |    Yes    || Outer scope only |    Yes    |
| Attention signal            |     Batch     | No  | Yes ||       No         |    n/a    |
| Informational/warning msgs  |    Nothing    |    No     ||       No         |    n/a    |
| Uncatchable errors          |    Varying    |  Varying  ||       No         |    n/a    |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+

A última coluna da tabela responde à pergunta. Com TRY-CATCHe com XACT_ABORT ONa transação está condenada em todos os casos possíveis.

Uma observação fora do escopo da pergunta. Como diz Erland, essa consistência é uma das razões para definir XACT_ABORTa ON:

Eu já dei a recomendação de que seus procedimentos armazenados devem incluir o comando SET XACT_ABORT, NOCOUNT ON. Se você olhar para a tabela acima, verá que, com XACT_ABORTefeito, há um nível mais alto de consistência. Por exemplo, a transação está sempre condenada. A seguir, vou mostrar muitos exemplos onde eu definir XACT_ABORTa OFF, de modo que você possa ter uma compreensão de por que você deve evitar essa configuração padrão.

Vladimir Baranov
fonte
7

Eu abordaria isso de forma diferente. XACT_ABORT_ONé uma marreta, você pode usar uma abordagem mais refinada, consulte Manipulação de exceção e transações aninhadas :

create procedure [usp_my_procedure_name]
as
begin
    set nocount on;
    declare @trancount int;
    set @trancount = @@trancount;
    begin try
        if @trancount = 0
            begin transaction
        else
            save transaction usp_my_procedure_name;

        -- Do the actual work here

lbexit:
        if @trancount = 0   
            commit;
    end try
    begin catch
        declare @error int, @message varchar(4000), @xstate int;
        select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
        if @xstate = -1
            rollback;
        if @xstate = 1 and @trancount = 0
            rollback
        if @xstate = 1 and @trancount > 0
            rollback transaction usp_my_procedure_name;

        raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
    end catch   
end
go

Essa abordagem reverterá, quando possível, apenas o trabalho realizado dentro do bloco TRY, e restaurará o estado para o estado antes de entrar no bloco TRY. Dessa forma, você pode executar um processamento complexo, como iterar um cursor, sem perder todo o trabalho em caso de erro. A única desvantagem é que, ao usar pontos de salvamento de transação, você não pode usar nada que seja incompatível com pontos de salvamento, como transações distribuídas.

Remus Rusanu
fonte
Agradeço sua resposta, mas a questão não é realmente se devemos definir XACT_ABORTcomo ONou OFF.
Vladimir Baranov 11/02
7

TL; DR / Resumo executivo: Com relação a esta parte da pergunta:

Não vejo em que casos o controle pode ser passado para dentro de CATCHuma transação que pode ser confirmada quando XACT_ABORTdefinida comoON .

Eu já testei bastante sobre isso agora e não consigo encontrar nenhum caso em que XACT_STATE()retorne 1dentro de um CATCHbloco quando @@TRANCOUNT > 0 e a propriedade da sessão XACT_ABORTé ON. De fato, de acordo com a página atual do MSDN para SET XACT_ABORT :

Quando SET XACT_ABORT está ativado, se uma instrução Transact-SQL gerar um erro em tempo de execução, a transação inteira será encerrada e revertida.

Essa declaração parece estar de acordo com sua especulação e minhas descobertas.

O artigo da MSDN sobre SET XACT_ABORTtem um exemplo quando algumas instruções dentro de uma transação são executadas com êxito e outras falham quando XACT_ABORTé definido comoOFF

Verdade, mas as instruções nesse exemplo não estão dentro de um TRYbloco. Essas mesmas declarações dentro de um TRYbloco ainda evitar a execução por quaisquer declarações após o que causou o erro, mas assumindo que XACT_ABORTé OFF, quando o controle é passado para o CATCHbloco a transação ainda está fisicamente válido na medida em que todas as mudanças anteriores aconteceu sem erro e podem ser comprometidos, se esse for o desejo, ou podem ser revertidos. Por outro lado, se XACT_ABORTé ON, em seguida, quaisquer alterações anteriores são automaticamente revertidas, e , em seguida, é-lhe dada a opção de: a) emitir umaROLLBACKque é basicamente apenas uma aceitação da situação, pois a transação já foi revertida, menos a redefinição @@TRANCOUNTpara 0, ou b) ocorreu um erro. Não é muita escolha, não é?

Um detalhe possivelmente importante para esse quebra-cabeça que não é aparente nessa documentação SET XACT_ABORTé que essa propriedade da sessão e até mesmo o código de exemplo existe desde o SQL Server 2000 (a documentação é quase idêntica entre as versões), antecedido a TRY...CATCHconstrução que foi introduzido no SQL Server 2005. Examinar novamente essa documentação e examinar o exemplo ( sem o TRY...CATCH), usar XACT_ABORT ONcausa uma reversão imediata da Transação: não há estado de Transação "não-comprometível" (observe que não há menção em todo um estado de transação "não-comprometível" nesseSET XACT_ABORT documentação).

Eu acho que é razoável concluir que:

  1. a introdução da TRY...CATCHconstrução no SQL Server 2005 criou a necessidade de um novo estado de transação (ou seja, "não comprometível") e a XACT_STATE()função de obter essas informações.
  2. o check- XACT_STATE()in de um CATCHbloco realmente só faz sentido se as duas opções a seguir forem verdadeiras:
    1. XACT_ABORTé OFF(o resto XACT_STATE()sempre deve retornar -1e @@TRANCOUNTseria tudo o que você precisa)
    2. Você tem lógica no CATCHbloco ou em algum lugar da cadeia, se as chamadas estiverem aninhadas, que faz uma alteração (uma COMMITou mesmo qualquer instrução DML, DDL, etc) em vez de fazer a ROLLBACK. (este é um caso de uso muito atípico) ** consulte a observação na parte inferior, na seção UPDATE 3, referente a uma recomendação não oficial da Microsoft de sempre verificar em XACT_STATE()vez de @@TRANCOUNTe por que os testes mostram que o raciocínio deles não se concretiza.
  3. a introdução da TRY...CATCHconstrução no SQL Server 2005 obsoleta a XACT_ABORT ONpropriedade da sessão, pois proporciona um maior grau de controle sobre a transação (você pelo menos tem a opção COMMIT, desde que XACT_STATE()não retorne -1).
    Outra maneira de analisar isso é que, antes do SQL Server 2005 , XACT_ABORT ONfornecia uma maneira fácil e confiável de interromper o processamento quando ocorreu um erro, em comparação com a verificação @@ERRORapós cada instrução.
  4. O código de exemplo da documentação para XACT_STATE()é incorreto ou, na melhor das hipóteses, enganoso, pois mostra a verificação de XACT_STATE() = 1quando XACT_ABORTé ON.

A parte longa ;-)

Sim, esse exemplo de código no MSDN é um pouco confuso (consulte também: @@ TRANCOUNT (reversão) vs. XACT_STATE ) ;-). E acho que isso é enganoso, porque mostra algo que não faz sentido (pelo motivo pelo qual você está perguntando: você pode até ter uma transação "comprometível" no CATCHbloco quando XACT_ABORTestá ON) ou, mesmo que seja possível? ainda se concentra em uma possibilidade técnica que poucos desejarão ou precisarão, e ignora o motivo pelo qual é mais provável que ela precise.

Se houver um erro grave o suficiente dentro do bloco TRY, o controle passará para CATCH. Então, se eu estou dentro do CATCH, sei que a transação teve um problema e, na verdade, a única coisa sensata a fazer nesse caso é reverter isso, não é?

Acho que ajudaria se tivéssemos certeza de que estamos na mesma página em relação ao significado de certas palavras e conceitos:

  • "erro grave o suficiente": para ficar claro, TRY ... CATCH interceptará a maioria dos erros. A lista do que não será capturado está listada na página vinculada do MSDN, na seção "Erros não afetados por uma construção TRY… CATCH".

  • "se eu estou dentro do CATCH, sei que a transação teve um problema" (em phas é adicionada): se por "transação" você quer dizer a unidade lógica de trabalho conforme determinada por você agrupando instruções em uma transação explícita, Muito provavelmente sim. Eu acho que a maioria de nós, membros do banco de dados, tenderia a concordar que a reversão é "a única coisa sensata a se fazer", pois provavelmente temos uma visão semelhante de como e por que usamos transações explícitas e concebemos quais etapas devem formar uma unidade atômica de trabalho.

    Mas, se você quer dizer as unidades de trabalho reais que estão sendo agrupadas na transação explícita, não, não sabe que a transação em si teve um problema. Você sabe apenas que uma instrução em execução na transação explicitamente definida gerou um erro. Mas pode não ser uma instrução DML ou DDL. E mesmo que fosse uma instrução DML, a própria transação ainda pode ser confirmada.

Dado os dois pontos mencionados acima, provavelmente devemos fazer uma distinção entre transações que você "não pode" confirmar e aquelas que "não deseja" confirmar.

Quando XACT_STATE()retorna a 1, isso significa que a Transação é "confirmada", que você pode escolher entre COMMITou ROLLBACK. Você pode não querer confirmá-lo, mas se por algum motivo difícil de apresentar, por um motivo, você desejava, pelo menos poderia, porque algumas partes da Transação foram concluídas com êxito.

Mas quando XACT_STATE()retorna a -1, você realmente precisa, ROLLBACKporque parte da Transação entrou em um estado ruim. Agora, eu concordo que se o controle foi passado para o bloco CATCH, faz sentido o suficiente apenas verificar @@TRANCOUNT, porque mesmo que você possa confirmar a transação, por que você deseja?

Mas se você notar no topo do exemplo, a configuração XACT_ABORT ONmuda um pouco as coisas. Você pode ter um erro regular, depois de fazer BEGIN TRANisso, passará o controle para o bloco CATCH quando XACT_ABORTestiver OFFe XACT_STATE () retornará 1. MAS, se XACT_ABORT for ON, a transação será "abortada" (isto é, invalidada) por qualquer erro de erro e, em seguida XACT_STATE(), retornará -1. Nesse caso, parece inútil verificar XACT_STATE()dentro do CATCHbloco, pois sempre parece retornar um -1quando XACT_ABORTé ON.

Então, para que serve XACT_STATE()? Algumas pistas são:

  • A página do MSDN para TRY...CATCH, na seção "Transações não confirmadas e XACT_STATE", diz:

    Um erro que normalmente encerra uma transação fora de um bloco TRY faz com que uma transação entre em um estado não comprometível quando o erro ocorre dentro de um bloco TRY.

  • A página MSDN para SET XACT_ABORT , na seção "Comentários", diz:

    Quando SET XACT_ABORT está desativado, em alguns casos, apenas a instrução Transact-SQL que gerou o erro é revertida e a transação continua o processamento.

    e:

    XACT_ABORT deve estar ativado para instruções de modificação de dados em uma transação implícita ou explícita na maioria dos provedores OLE DB, incluindo o SQL Server.

  • A página MSDN para BEGIN TRANSACTION , na seção "Comentários", diz:

    A transação local iniciada pela instrução BEGIN TRANSACTION é escalada para uma transação distribuída se as seguintes ações forem executadas antes que a instrução seja confirmada ou revertida:

    • Uma instrução INSERT, DELETE ou UPDATE que faz referência a uma tabela remota em um servidor vinculado é executada. A instrução INSERT, UPDATE ou DELETE falhará se o provedor OLE DB usado para acessar o servidor vinculado não suportar a interface ITransactionJoin.

O uso mais aplicável parece estar dentro do contexto das instruções DML do Servidor Vinculado. E acredito que me deparei com isso anos atrás. Não me lembro de todos os detalhes, mas tinha algo a ver com o servidor remoto não estar disponível e, por algum motivo, esse erro não foi detectado no bloco TRY e nunca foi enviado ao CATCH. um COMMIT quando não deveria. Obviamente, isso poderia ter sido um problema de não ter XACT_ABORTdefinido, em ONvez de não ter verificado XACT_STATE(), ou possivelmente ambos. Lembro-me de ler algo que dizia que se você usasse Servidores Vinculados e / ou Transações Distribuídas, precisava usar XACT_ABORT ONe / ou XACT_STATE(), mas não consigo encontrar esse documento agora. Se o encontrar, atualizarei isso com o link

Ainda assim, tentei várias coisas e não consigo encontrar um cenário que tenha XACT_ABORT ONe passe o controle para o CATCHbloco com os XACT_STATE()relatórios 1.

Experimente estes exemplos para ver o efeito de XACT_ABORTno valor de XACT_STATE():

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    SELECT 1/0 AS [DivideByZero]; -- error, yo!
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]
END CATCH;

ATUALIZAR

Embora não faça parte da pergunta original, com base nesses comentários nesta resposta:

Estive lendo os artigos de Erland sobre Manuseio de erros e transações, onde ele diz que XACT_ABORTé OFFpor padrão por motivos herdados e normalmente devemos configurá-lo ON.
...
"... se você seguir a recomendação e executar com SET XACT_ABORT ON, a transação estará sempre condenada."

Antes de usar em XACT_ABORT ONqualquer lugar, eu perguntava: o que exatamente está sendo ganho aqui? Eu não achei necessário fazer isso e geralmente advogo que você deve usá-lo somente quando necessário. Se você deseja ou não ROLLBACKlidar com facilidade o suficiente, usando o modelo mostrado na resposta do @ Remus ou o modelo que venho usando há anos que é essencialmente a mesma coisa, mas sem o Save Point, como mostrado nesta resposta (que lida com chamadas aninhadas):

Somos obrigados a lidar com a transação no código C #, bem como no procedimento armazenado


ATUALIZAÇÃO 2

Fiz um pouco mais de teste, desta vez, criando um pequeno aplicativo .NET Console, criando uma transação na camada de aplicativo, antes de executar qualquer SqlCommandobjeto (por exemplo using (SqlTransaction _Tran = _Connection.BeginTransaction()) { ..., via ), além de usar um erro de interrupção de lote em vez de apenas uma declaração erro de interrupção e descobriu que:

  1. Uma transação "não confirmada" é aquela que já foi, em grande parte, revertida (as alterações foram desfeitas), mas @@TRANCOUNTainda é> 0.
  2. Quando você tem uma transação "não confirmada", não pode emitir uma, COMMITpois isso gerará um erro dizendo que a transação é "não confirmada". Você também não pode ignorá-lo / não fazer nada, pois um erro será gerado quando o lote terminar, declarando que o lote foi concluído com uma transação persistente e não comprometida e será revertido (portanto, hum, se ele for revertido de qualquer maneira, por que se preocupar em lançar o erro?). Portanto, você deve emitir um explícito ROLLBACK, talvez não no CATCHbloco imediato , mas antes que o lote termine.
  3. Em uma TRY...CATCHconstrução, quando XACT_ABORThouver OFF, os erros que encerrariam a Transação automaticamente, caso ocorressem fora de um TRYbloco, como erros de interrupção de lote, desfarão o trabalho, mas não encerrarão a Tranasction, deixando-o como "não comprometível". A emissão de a ROLLBACKé mais uma formalidade necessária para encerrar a transação, mas o trabalho já foi revertido.
  4. Quando isso XACT_ABORTocorre ON, a maioria dos erros atua como interrupção de lote e, portanto, se comporta como descrito no item acima (nº 3).
  5. XACT_STATE(), pelo menos em um CATCHbloco, mostrará um -1erro de interrupção de lote se houver uma transação ativa no momento do erro.
  6. XACT_STATE()às vezes retorna 1mesmo quando não há transação ativa. Se @@SPID(entre outros) estiver na SELECTlista junto com XACT_STATE(), XACT_STATE()retornará 1 quando não houver Transação ativa. Esse comportamento foi iniciado no SQL Server 2012 e existe em 2014, mas não testei em 2016.

Com os pontos acima em mente:

  • Dados os pontos 4 e 5, uma vez que a maioria (ou todos?) Dos erros tornará uma transação "incompatível", parece totalmente inútil verificar XACT_STATE()o CATCHbloco quando XACT_ABORTé ONque o valor retornado será sempre -1.
  • Verificando XACT_STATE()no CATCHbloco quando XACT_ABORTé OFFfaz mais sentido porque o valor de retorno, pelo menos, ter alguma variação, uma vez que irá retornar 1para erros de abortar declaração. No entanto, se você codifica como a maioria de nós, essa distinção não faz sentido, pois você estará ligando de ROLLBACKqualquer maneira simplesmente pelo fato de que ocorreu um erro.
  • Se você encontrar uma situação que faz mandado de emitir um COMMITno CATCHbloco, em seguida, verificar o valor XACT_STATE(), e certifique-se SET XACT_ABORT OFF;.
  • XACT_ABORT ONparece oferecer pouco ou nenhum benefício sobre a TRY...CATCHconstrução.
  • Não consigo encontrar um cenário em que a verificação XACT_STATE()ofereça um benefício significativo sobre a simples verificação @@TRANCOUNT.
  • Também não consigo encontrar um cenário em que XACT_STATE()retorne 1em um CATCHbloco quando XACT_ABORTestiver ON. Eu acho que é um erro de documentação.
  • Sim, você pode reverter uma transação que você não iniciou explicitamente. E, no contexto do uso XACT_ABORT ON, é um ponto discutível, pois um erro que ocorre em um TRYbloco reverterá automaticamente as alterações.
  • A TRY...CATCHconstrução tem o benefício de XACT_ABORT ONnão cancelar automaticamente toda a transação e, portanto, permitir que a transação (desde que XACT_STATE()retorne 1) seja confirmada (mesmo que esse seja um caso extremo).

Exemplo de XACT_STATE()retorno -1quando XACT_ABORTé OFF:

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT CONVERT(INT, 'g') AS [ConversionError];

    COMMIT TRAN;
END TRY
BEGIN CATCH
    DECLARE @State INT;
    SET @State = XACT_STATE();
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            @State AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage];

    IF (@@TRANCOUNT > 0)
    BEGIN
        SELECT 'Rollin back...' AS [Transaction];
        ROLLBACK;
    END;
END CATCH;

ATUALIZAÇÃO 3

Relacionado ao item # 6 na seção UPDATE 2 (ou seja, possível valor incorreto retornado XACT_STATE()quando não há transação ativa):

  • O comportamento estranho / incorreto iniciado no SQL Server 2012 (até agora testado em relação ao 2012 SP2 e 2014 SP1)
  • Nas versões 2005, 2008 e 2008 R2 do SQL Server, XACT_STATE()não relatavam valores esperados quando usados ​​em gatilhos ou INSERT...EXECcenários: xact_state () não pode ser usado com confiabilidade para determinar se uma transação está condenada . No entanto, nessas três versões (eu testei apenas no 2008 R2), XACT_STATE()não é relatado incorretamente 1quando usado em um SELECTcom @@SPID.
  • Há um bug do Connect arquivado no comportamento mencionado aqui, mas está fechado como "Por Design": XACT_STATE () pode retornar um estado de transação incorreto no SQL 2012 . No entanto, o teste foi realizado ao selecionar a partir de uma DMV e concluiu-se que isso naturalmente teria uma transação gerada pelo sistema, pelo menos para algumas DMVs. Também foi declarado na resposta final dos Estados Unidos que:

    Observe que uma instrução IF e também uma SELECT sem FROM não iniciam uma transação.
    por exemplo, executar SELECT XACT_STATE () se você não tiver uma transação existente anteriormente retornará 0.

    Essas instruções estão incorretas, dado o seguinte exemplo:

    SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @@SPID AS [SPID];
    GO
    DECLARE @SPID INT;
    SET @SPID = @@SPID;
    SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @SPID AS [SPID];
    GO
  • Portanto, o novo bug do Connect:
    XACT_STATE () retorna 1 quando usado no SELECT com algumas variáveis ​​do sistema, mas sem a cláusula FROM

OBSERVAÇÃO: no item "XACT_STATE () pode retornar um estado de transação incorreto no SQL 2012", o item de conexão vinculado diretamente acima, a Microsoft (assim, um representante de) afirma:

@@ trancount retorna o número de instruções BEGIN TRAN. Portanto, não é um indicador confiável se existe uma transação ativa. XACT_STATE () também retornará 1 se houver uma transação de confirmação automática ativa e, portanto, é um indicador mais confiável de se há uma transação ativa.

No entanto, não encontro motivos para não confiar @@TRANCOUNT. O teste a seguir mostra que @@TRANCOUNTrealmente retorna 1em uma transação de confirmação automática:

--- begin setup
GO
CREATE PROCEDURE #TransactionInfo AS
SET NOCOUNT ON;
SELECT @@TRANCOUNT AS [TranCount],
       XACT_STATE() AS [XactState];
GO
--- end setup

DECLARE @Test TABLE (TranCount INT, XactState INT);

SELECT * FROM @Test; -- no rows

EXEC #TransactionInfo; -- 0 for both fields

INSERT INTO @Test (TranCount, XactState)
    EXEC #TransactionInfo;

SELECT * FROM @Test; -- 1 row; 1 for both fields

Também testei em uma tabela real com um gatilho e, @@TRANCOUNTno gatilho, relatei com precisão 1mesmo que nenhuma transação explícita tivesse sido iniciada.

Solomon Rutzky
fonte
4

A programação defensiva exige que você escreva um código que lide com o maior número possível de estados conhecidos, reduzindo assim a possibilidade de erros.

Verificar XACT_STATE () para determinar se uma reversão pode ser executada é simplesmente uma boa prática. Tentar cegamente uma reversão significa que você pode inadvertidamente causar um erro dentro do seu TRY ... CATCH.

Uma maneira de uma reversão falhar dentro de uma TRY ... CATCH seria se você não explicitamente iniciar uma transação. Copiar e colar blocos de código pode facilmente causar isso.

Max Vernon
fonte
Obrigado pela resposta. Eu simplesmente não conseguia pensar em um caso em que o simples ROLLBACKnão funcionasse dentro do CATCHe você deu um bom exemplo. Eu acho que também pode se tornar rapidamente confuso se transações aninhadas e procedimentos armazenados aninhados TRY ... CATCH ... ROLLBACKestiverem envolvidos.
Vladimir Baranov 08/02
Ainda assim, eu apreciaria se você pudesse estender sua resposta em relação à segunda parte - IF (XACT_STATE()) = 1 COMMIT TRANSACTION; Como podemos acabar dentro do CATCHbloco com transações comprometíveis? Eu não ousaria cometer algum (possível) lixo de dentro do CATCH. Meu raciocínio é: se estamos dentro de CATCHalgo que deu errado, não posso confiar no estado do banco de dados, então seria melhor ROLLBACKo que quer que tenhamos.
Vladimir Baranov 08/02