Introdução
Recentemente, um colega meu veio até mim em desespero confessando que ele havia emitido uma declaração de atualização sem uma cláusula WHERE em uma tabela de aplicativos de chave. As implicações no front-end seriam terríveis, então ele veio a mim diretamente porque precisava urgentemente de ajuda para reverter a situação de qualquer maneira antes que os e-mails e a escalada começassem a chegar.
Quando analisamos a situação, descobrimos que as alterações não foram aplicadas no banco de dados secundário. Na maioria dos casos, o atraso entre nossos bancos de dados primários e secundários é de vinte minutos (temos um pouco desconcertante para evitar problemas de desempenho). Como meu colega pediu ajuda imediatamente após perceber o erro, conseguimos recuperar os dados do banco de dados secundário. Descrevi o valor de tal atraso neste artigo .
Revisão do cenário
O cenário que descrevi acima não é incomum. Uma das razões pelas quais isso acontece com usuários regulares do SQL Server é que o SQL Server usa o que é chamado de Transações Implícitas. As transações implícitas são desativadas por padrão, o que significa que o SQL Server não espera que você emita uma instrução COMMIT TRANSACTION no final de cada instrução. Na verdade, cada instrução é confirmada automaticamente. Isso é conveniente e ajuda a evitar situações em que sessões que ainda não foram confirmadas acabam bloqueando recursos e impactando o desempenho. Brent Ozar fornece mais detalhes sobre as implicações de desempenho de IMPLICIT TRANSACTIONS =ON.
No entanto, uma pequena desvantagem dessa configuração (IMPLICIT TRANSACTIONS =OFF) é que os usuários não têm a oportunidade de repensar uma declaração e emitir um ROLLBACK, o que é muito comum no Oracle. A Fig. 1 mostra as opções de consulta ANSI disponíveis no SQL Server Management Studio.
Fig. 1 Padrões ANSI no SQL Server Management Studio
Uso de transações implícitas
Em essência, o problema que enfrentamos nessa configuração padrão ou em nossa ferramenta cliente mais desejável é que não podemos ROLLBACK depois de executar uma instrução SQL. Podemos contornar isso habilitando TRANSAÇÕES IMPLÍCITAS em nossa sessão. Isso nos dará a oportunidade de ROLLBACK transações, se necessário. A Fig. 2 e a Fig. 4 nos mostram que podemos ter essa configuração ativada apenas para uma sessão, mesmo que isso não elimine o risco de a sessão do usuário bloquear outras se um ROLLBACK ou COMMIT não for emitido.
Fig. 2 TRANSAÇÕES IMPLÍCITAS LIGADAS em uma sessão
-- Listing 1: UPDATE Table TAB2 with IMPLICIT_TRANSACTIONS ON SET IMPLICIT_TRANSACTIONS ON DECLARE @IMPLICIT_TRANSACTIONS VARCHAR(3) = 'OFF'; IF ( (2 & @@OPTIONS) = 2 ) SET @IMPLICIT_TRANSACTIONS = 'ON'; SELECT @IMPLICIT_TRANSACTIONS AS IMPLICIT_TRANSACTIONS; USE KTrain GO SELECT * FROM Tab2; GO UPDATE TAB2 SET countryCode='SA' -- WHERE fname='Joyce'; GO SELECT * FROM Tab2; GO
Fig. 3 Todas as linhas atualizadas
Para ilustrar a solução alternativa descrita aqui, vejamos o código SQL na Listagem 1. Vamos supor que um usuário regular do SQL Server, um desenvolvedor júnior, tenha recebido um conjunto de scripts para executar sob determinadas condições . No script, a cláusula WHERE foi comentada porque é esperado que cada vez que eles executem este script, eles alterem o predicado. Claro, este é um caso de uso simples e o risco pode ser tratado de várias maneiras, mas queremos apenas mostrar a possibilidade de realizar um ROLLBACK.
Lembre-se de que já ativamos IMPLICIT TRANSACTION, portanto, quando executarmos essa instrução, o SQL Server estará esperando que façamos COMMIT ou ROLLBACK a transação. A intenção do desenvolvedor é atualizar o countryCode de Joyce Afam para 'SA' desde que ela imigrou para a África do Sul. A Fig. 3 mostra que o desenvolvedor, ao tentar fazer isso, atualizou acidentalmente todas as linhas com o valor SA como countryCode . Eles percebem isso e emitem um ROLLBACK.
Fig. 4 Emissão de ROLLBACK
Fig. 5 TRANSAÇÕES IMPLÍCITAS LIGADAS em outra sessão
No entanto, na outra sessão em que não ativamos TRANSAÇÕES IMPLÍCITAS, descobrimos que o desenvolvedor não conseguiu se recuperar do erro. Eles não podem emitir com sucesso um ROLLBACK neste caso. A recuperação implicaria então a restauração de dados.
Fig. 6 ROLLBACK não é possível sem TRANSAÇÕES IMPLÍCITAS LIGADAS
Usando transações explícitas
Outra abordagem para obter o mesmo efeito é incluir o DML em uma transação declarando explicitamente BEGIN TRAN. Novamente, é muito importante concluir a transação – usando COMMIT ou ROLLBACK. No contexto desta discussão, emitimos um ROLLBACK, pois percebemos que há um erro no código.
-- Listing 2: UPDATE Table TAB2 with Explicit Transaction BEGIN TRAN GO USE KTrain GO SELECT * FROM Tab2; GO UPDATE TAB2 SET countryCode='GH' -- WHERE fname='Joyce'; GO SELECT * FROM Tab2; GO ROLLBACK; SELECT * FROM Tab2; GO - Listing 3: Corrected UPDATE Statement BEGIN TRAN GO USE KTrain GO SELECT * FROM Tab2; GO UPDATE TAB2 SET countryCode='SA' WHERE fname='Joyce'; GO SELECT * FROM Tab2; GO
Conclusão
Neste artigo, abordamos brevemente uma boa solução alternativa para criar oportunidades de ROLLBACK e, assim, mitigar os erros do usuário resultantes de DML incorreto. Também destacamos um risco importante dessa abordagem, que é o bloqueio inadvertido. Um DBA pode iniciar investigações sobre a possível presença desse risco consultando sys.dm_tran_session_transactions, sys.dm_tran_locks e objetos de gerenciamento dinâmico semelhantes.
Referências
Corrigindo a perda de dados usando o envio de logs com recuperação atrasada
Definir transações implícitas
Definir transações implícitas como uma má ideia
DMVs para transações