Eu tenho lido o MSDN sobre TRY...CATCH
e XACT_STATE
.
Ele tem o exemplo a seguir que usa XACT_STATE
no CATCH
bloco de uma TRY…CATCH
construçã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_STATE
retorna?
Observe que o sinalizador XACT_ABORT
está definido ON
no exemplo.
Se houver um erro grave o suficiente dentro do TRY
bloco, 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 CATCH
e 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 CATCH
uma transação que pode ser confirmada quando XACT_ABORT
definida comoON
.
O artigo do MSDN sobre SET XACT_ABORT
tem 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 ON
como pode acontecer que XACT_STATE()
retorne 1 dentro do CATCH
bloco?
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 ON
o CATCH
bloco 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
fonte
XACT_ABORT
comoON
ouOFF
.TL; DR / Resumo executivo: Com relação a esta parte da pergunta:
Eu já testei bastante sobre isso agora e não consigo encontrar nenhum caso em que
XACT_STATE()
retorne1
dentro de umCATCH
bloco quando@@TRANCOUNT > 0
e a propriedade da sessãoXACT_ABORT
éON
. De fato, de acordo com a página atual do MSDN para SET XACT_ABORT :Essa declaração parece estar de acordo com sua especulação e minhas descobertas.
Verdade, mas as instruções nesse exemplo não estão dentro de um
TRY
bloco. Essas mesmas declarações dentro de umTRY
bloco ainda evitar a execução por quaisquer declarações após o que causou o erro, mas assumindo queXACT_ABORT
éOFF
, quando o controle é passado para oCATCH
bloco 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, seXACT_ABORT
éON
, em seguida, quaisquer alterações anteriores são automaticamente revertidas, e , em seguida, é-lhe dada a opção de: a) emitir umaROLLBACK
que é basicamente apenas uma aceitação da situação, pois a transação já foi revertida, menos a redefinição@@TRANCOUNT
para0
, 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 aTRY...CATCH
construção que foi introduzido no SQL Server 2005. Examinar novamente essa documentação e examinar o exemplo ( sem oTRY...CATCH
), usarXACT_ABORT ON
causa 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:
TRY...CATCH
construção no SQL Server 2005 criou a necessidade de um novo estado de transação (ou seja, "não comprometível") e aXACT_STATE()
função de obter essas informações.XACT_STATE()
in de umCATCH
bloco realmente só faz sentido se as duas opções a seguir forem verdadeiras:XACT_ABORT
éOFF
(o restoXACT_STATE()
sempre deve retornar-1
e@@TRANCOUNT
seria tudo o que você precisa)CATCH
bloco ou em algum lugar da cadeia, se as chamadas estiverem aninhadas, que faz uma alteração (umaCOMMIT
ou mesmo qualquer instrução DML, DDL, etc) em vez de fazer aROLLBACK
. (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 emXACT_STATE()
vez de@@TRANCOUNT
e por que os testes mostram que o raciocínio deles não se concretiza.TRY...CATCH
construção no SQL Server 2005 obsoleta aXACT_ABORT ON
propriedade da sessão, pois proporciona um maior grau de controle sobre a transação (você pelo menos tem a opçãoCOMMIT
, desde queXACT_STATE()
não retorne-1
).Outra maneira de analisar isso é que, antes do SQL Server 2005 ,
XACT_ABORT ON
fornecia uma maneira fácil e confiável de interromper o processamento quando ocorreu um erro, em comparação com a verificação@@ERROR
após cada instrução.XACT_STATE()
é incorreto ou, na melhor das hipóteses, enganoso, pois mostra a verificação deXACT_STATE() = 1
quandoXACT_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
CATCH
bloco quandoXACT_ABORT
está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.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 a1
, isso significa que a Transação é "confirmada", que você pode escolher entreCOMMIT
ouROLLBACK
. 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,ROLLBACK
porque 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 ON
muda um pouco as coisas. Você pode ter um erro regular, depois de fazerBEGIN TRAN
isso, passará o controle para o bloco CATCH quandoXACT_ABORT
estiverOFF
e XACT_STATE () retornará1
. MAS, se XACT_ABORT forON
, a transação será "abortada" (isto é, invalidada) por qualquer erro de erro e, em seguidaXACT_STATE()
, retornará-1
. Nesse caso, parece inútil verificarXACT_STATE()
dentro doCATCH
bloco, pois sempre parece retornar um-1
quandoXACT_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:A página MSDN para SET XACT_ABORT , na seção "Comentários", diz:
e:
A página MSDN para BEGIN TRANSACTION , na seção "Comentários", diz:
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_ABORT
definido, emON
vez de não ter verificadoXACT_STATE()
, ou possivelmente ambos. Lembro-me de ler algo que dizia que se você usasse Servidores Vinculados e / ou Transações Distribuídas, precisava usarXACT_ABORT ON
e / ouXACT_STATE()
, mas não consigo encontrar esse documento agora. Se o encontrar, atualizarei isso com o linkAinda assim, tentei várias coisas e não consigo encontrar um cenário que tenha
XACT_ABORT ON
e passe o controle para oCATCH
bloco com osXACT_STATE()
relatórios1
.Experimente estes exemplos para ver o efeito de
XACT_ABORT
no valor deXACT_STATE()
:ATUALIZAR
Embora não faça parte da pergunta original, com base nesses comentários nesta resposta:
Antes de usar em
XACT_ABORT ON
qualquer 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ãoROLLBACK
lidar 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
SqlCommand
objeto (por exemplousing (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:@@TRANCOUNT
ainda é> 0.COMMIT
pois 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ícitoROLLBACK
, talvez não noCATCH
bloco imediato , mas antes que o lote termine.TRY...CATCH
construção, quandoXACT_ABORT
houverOFF
, os erros que encerrariam a Transação automaticamente, caso ocorressem fora de umTRY
bloco, 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 aROLLBACK
é mais uma formalidade necessária para encerrar a transação, mas o trabalho já foi revertido.XACT_ABORT
ocorreON
, a maioria dos erros atua como interrupção de lote e, portanto, se comporta como descrito no item acima (nº 3).XACT_STATE()
, pelo menos em umCATCH
bloco, mostrará um-1
erro de interrupção de lote se houver uma transação ativa no momento do erro.XACT_STATE()
às vezes retorna1
mesmo quando não há transação ativa. Se@@SPID
(entre outros) estiver naSELECT
lista junto comXACT_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:
XACT_STATE()
oCATCH
bloco quandoXACT_ABORT
éON
que o valor retornado será sempre-1
.XACT_STATE()
noCATCH
bloco quandoXACT_ABORT
éOFF
faz mais sentido porque o valor de retorno, pelo menos, ter alguma variação, uma vez que irá retornar1
para 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 deROLLBACK
qualquer maneira simplesmente pelo fato de que ocorreu um erro.COMMIT
noCATCH
bloco, em seguida, verificar o valorXACT_STATE()
, e certifique-seSET XACT_ABORT OFF;
.XACT_ABORT ON
parece oferecer pouco ou nenhum benefício sobre aTRY...CATCH
construção.XACT_STATE()
ofereça um benefício significativo sobre a simples verificação@@TRANCOUNT
.XACT_STATE()
retorne1
em umCATCH
bloco quandoXACT_ABORT
estiverON
. Eu acho que é um erro de documentação.XACT_ABORT ON
, é um ponto discutível, pois um erro que ocorre em umTRY
bloco reverterá automaticamente as alterações.TRY...CATCH
construção tem o benefício deXACT_ABORT ON
não cancelar automaticamente toda a transação e, portanto, permitir que a transação (desde queXACT_STATE()
retorne1
) seja confirmada (mesmo que esse seja um caso extremo).Exemplo de
XACT_STATE()
retorno-1
quandoXACT_ABORT
éOFF
: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):XACT_STATE()
não relatavam valores esperados quando usados em gatilhos ouINSERT...EXEC
cená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 incorretamente1
quando usado em umSELECT
com@@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:
Essas instruções estão incorretas, dado o seguinte exemplo:
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:
No entanto, não encontro motivos para não confiar
@@TRANCOUNT
. O teste a seguir mostra que@@TRANCOUNT
realmente retorna1
em uma transação de confirmação automática:Também testei em uma tabela real com um gatilho e,
@@TRANCOUNT
no gatilho, relatei com precisão1
mesmo que nenhuma transação explícita tivesse sido iniciada.fonte
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.
fonte
ROLLBACK
não funcionasse dentro doCATCH
e você deu um bom exemplo. Eu acho que também pode se tornar rapidamente confuso se transações aninhadas e procedimentos armazenados aninhadosTRY ... CATCH ... ROLLBACK
estiverem envolvidos.IF (XACT_STATE()) = 1 COMMIT TRANSACTION;
Como podemos acabar dentro doCATCH
bloco com transações comprometíveis? Eu não ousaria cometer algum (possível) lixo de dentro doCATCH
. Meu raciocínio é: se estamos dentro deCATCH
algo que deu errado, não posso confiar no estado do banco de dados, então seria melhorROLLBACK
o que quer que tenhamos.