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:
- 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.
- o check-
XACT_STATE()in de um CATCHbloco realmente só faz sentido se as duas opções a seguir forem verdadeiras:
XACT_ABORTé OFF(o resto XACT_STATE()sempre deve retornar -1e @@TRANCOUNTseria tudo o que você precisa)
- 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.
- 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.
- 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:
- Uma transação "não confirmada" é aquela que já foi, em grande parte, revertida (as alterações foram desfeitas), mas
@@TRANCOUNTainda é> 0.
- 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.
- 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.
- 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).
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.
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.
XACT_ABORTcomoONouOFF.