Recentemente, escrevi um script T-SQL para realizar algumas atualizações e inserções em 3 tabelas diferentes. Eu queria que isso fosse feito em uma única transação, então li a documentação da Microsoft sobre como usar transações explícitas com try/catch.
De acordo com a documentação , pode ser feito assim:
BEGIN TRANSACTION;
BEGIN TRY
-- Generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_STATE() AS ErrorState
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_LINE() AS ErrorLine
,ERROR_MESSAGE() AS ErrorMessage;
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH;
IF @@TRANCOUNT > 0
COMMIT TRANSACTION;
GO
Portanto, implementei esse padrão e coloquei todas as minhas atualizações e inserções na cláusula TRY. O problema é que o código conseguiu deixar uma transação aberta após o término da consulta, e não vejo como isso aconteceria. Independentemente do trabalho feito dentro da cláusula TRY, quais cenários possíveis poderiam fazer com que uma consulta como essa deixasse uma transação aberta?
Existem alguns tipos de erros que não são detectados pela estrutura
TRY
/ T-SQLCATCH
(retirados da página MSDN para TRY...CATCH e ligeiramente editados para maior clareza):Para o código de exemplo fornecido na pergunta (ou seja, uma única instrução DML), há uma correção muito simples: remova a transação explícita. Para uma única instrução DML, uma transação explícita é desnecessária, a menos que haja lógica adicional que verifique alguma condição e, opcionalmente, decida desfazer essa alteração por meio de um
ROLLBACK
feito no principalTRY
e não como parte doCATCH
, pois não era um mecanismo de banco de dados erro. Fora desse cenário específico, não consigo ver uma razão (boa, pelo menos) para usar uma transação explícita para uma única instrução DML. Mas eu ainda manteria a estruturaTRY
/CATCH
para tratamento geral de erros.Quando se trata de várias instruções DML (que é o verdadeiro contexto da pergunta, conforme declarado na redação acima do código de exemplo), obviamente você precisa de uma transação explícita, então aqui estão alguns pensamentos sobre isso:
sys.dm_exec_connections
.O pool de conexão complica o tratamento de reversão automática, pois, por design, mantém a conexão aberta, mesmo depois que o cliente a "fecha". Quando a conexão permanece aberta para que outra tentativa de "conexão" possa simplesmente reutilizá-la, a sessão em si não é "limpa" até que uma nova conexão seja aberta e um lote de consulta seja executado! Após a primeira execução enviada pelo código do aplicativo em uma "nova" conexão que está realmente reutilizando uma conexão do pool, um processo interno rotulado como
sp_reset_connection
é chamado que, entre outras coisas, reverterá uma transação não confirmada nessa sessão que agora é sendo reutilizado. O problema aqui é quando o pool de conexões está sendo usado e há um término do processo (talvez um Timeout de comando) enenhuma nova conexão está sendo solicitada e nenhuma nova consulta está sendo enviada. Nesse caso, a conexão fica apenas no pool e a sessão ainda existe e nenhuma operação de limpeza está sendo solicitada. Mas isso não dura para sempre (embora ainda mais do que você deseja nesses casos). De acordo com a página do MSDN para SQL Server Connection Pooling (ADO.NET) (na seção Removendo conexões ):CATCH
bloco. Mas cancelar uma execução no SSMS ainda mantém você na mesma sessão, então a transação ainda está ativa até que você chame manualmenteCOMMIT
ouROLLBACK
, ou até que você feche a guia de consulta (momento em que ele informará que há uma transação não confirmada e perguntará se você quer cometê-lo ou não; meu teste mostra que responder "não" reverterá).Então, o que fazer com o código do aplicativo que está usando o pool de conexões? Eu não sou um grande fã de usar arquivos
SET XACT_ABORT ON;
. O que eu tentaria primeiro é:As conexões podem definir o tamanho máximo do pool de conexões por meio da palavra-
Max Pool Size
chave da string de conexão. Se o pool for muito grande, é menos provável que qualquer conexão específica seja reutilizada rapidamente. O tamanho do pool padrão parece ser 100. Definir o valor para ser um pouco menor do que é atualmente (mas não muito baixo para não fazer uso efetivo do pool de conexões) ajudará a garantir que as conexões sejam reutilizadas mais rapidamente significará a limpeza processo será chamado mais rapidamente, o que reverterá qualquer transação aberta.No código do aplicativo onde você está executando a consulta/procedimento armazenado, no
catch
bloco você pode chamar SqlConnection.ClearPool que deve fechar a conexão real no nível do SQL Server que, por sua vez, deve permitir a reversão automática de transações não confirmadas. Você pode até ser direcionado com isso verificando a exceção para ver se é um tempo limite de comando ou um dos outros poucos cenários que encerrariam o processo ao ignorar oCATCH
bloco T-SQL e, em caso afirmativo , chameClearPool
.Tecnicamente, você pode simplesmente desabilitar o pool de conexões totalmente especificando
Pooling=false;
na string de conexão. No entanto, não acredito que isso seja necessário e não o recomendo, a menos que seja absolutamente necessário OU tenha um aplicativo que não gere muitas conexões em primeiro lugar.O cenário comum que faz com que a transação permaneça aberta é um tempo limite de comando. Um tempo limite de comando ocorre no cliente, não no servidor. Quando uma consulta é executada por mais tempo do que o especificado
CommandTimeout
, a API do cliente envia um comando de atenção ao servidor para cancelar o lote. Isso evita que qualquer código subsequente no lote, incluindo oCATCH
bloco, seja executado. A transação é deixada em aberto como resultado, a menos queSET XACT_ABORT ON
seja especificado.SET XACT_ABORT ON
A configuração faz com que a transação seja revertida após um erro T-SQL, então sugiro que você crie o hábito disso para todas as transações explícitas. Abaixo está um exemplo de padrão geral que eu recomendo. UseTHROW;
noCATCH
bloco no SQL Server 2012 e posterior, em vez daRAISERROR
feiúra.Esteja ciente de que erros de compilação no mesmo escopo não são afetados
SET XACT_ABORT
e não podem ser detectados.Cenários possíveis:
Análise (sintaxe e semântica)
Compilação (referência de objeto inválida, o erro atingirá o bloco catch se a instrução for armazenada em cache e sinalizada, mas não na primeira vez)
Tempo Limite do Comando