1. O problema que transações resolvem
Imagine uma transferência bancária: debitar R$ 100 da conta A e creditar R$ 100 na conta B.
São dois comandos UPDATE. Se o primeiro rodar e o segundo falhar
(queda de conexão, violação de constraint, o que for), o dinheiro simplesmente desaparece
— saiu da conta A e não chegou a lugar nenhum. Uma transação existe para impedir exatamente isso.
Atomicidade: tudo ou nada. Consistência: o banco sai de um
estado válido para outro estado válido. Isolamento: transações concorrentes não
se enxergam no meio do caminho. Durabilidade: uma vez commitada, a mudança
sobrevive mesmo a uma queda de energia. SQL Server garante as quatro — a sua parte é usar
BEGIN TRANSACTION corretamente.
2. Sintaxe básica
CREATE PROCEDURE dbo.usp_ContaTransferir
@ContaOrigemId INT,
@ContaDestinoId INT,
@Valor DECIMAL(10,2)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
UPDATE dbo.Conta SET Saldo = Saldo - @Valor WHERE Id = @ContaOrigemId;
UPDATE dbo.Conta SET Saldo = Saldo + @Valor WHERE Id = @ContaDestinoId;
COMMIT TRANSACTION;
END;
GO
BEGIN TRANSACTION abre um "envelope": nada dentro dele é gravado de
forma definitiva até COMMIT TRANSACTION. Se algo der errado antes do
COMMIT, ROLLBACK TRANSACTION desfaz tudo que aconteceu desde o BEGIN.
Ela não trata erro nenhum. Se o segundo UPDATE falhar (por exemplo,
@ContaDestinoId não existir e violar uma constraint), o SQL Server
por padrão não desfaz automaticamente o primeiro UPDATE — a transação fica aberta,
travando linhas, até alguém decidir o que fazer. Isso é resolvido na próxima seção.
3. TRY/CATCH + XACT_ABORT — a versão de produção
A combinação correta usa três peças: SET XACT_ABORT ON,
TRY/CATCH (aprofundado no próximo capítulo) e
ROLLBACK dentro do CATCH.
CREATE PROCEDURE dbo.usp_ContaTransferir
@ContaOrigemId INT,
@ContaDestinoId INT,
@Valor DECIMAL(10,2)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
UPDATE dbo.Conta SET Saldo = Saldo - @Valor WHERE Id = @ContaOrigemId;
UPDATE dbo.Conta SET Saldo = Saldo + @Valor WHERE Id = @ContaDestinoId;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
THROW;
END CATCH
END;
GO
Fig. 1 — Toda transação termina de uma das duas formas: as duas UPDATEs valem, ou nenhuma vale.
Explicando cada peça nova:
| Comando | Papel |
|---|---|
SET XACT_ABORT ON | Faz o SQL Server abortar e desfazer a transação automaticamente diante de qualquer erro de execução — sem isso, alguns erros deixam a transação aberta. |
@@TRANCOUNT | Conta quantas transações estão abertas no momento. Checar antes do ROLLBACK evita erro "sem transação para desfazer". |
THROW | Relança o erro original para quem chamou a procedure, depois do ROLLBACK — assunto aprofundado no capítulo 6. |
Adote SET NOCOUNT ON; SET XACT_ABORT ON; como as duas primeiras
linhas de toda procedure que faz alguma escrita (INSERT/UPDATE/DELETE). É o
tipo de hábito que evita um incidente de dados inconsistentes meses depois.
4. Transações aninhadas e @@TRANCOUNT
T-SQL permite "aninhar" BEGIN TRANSACTION, mas isso é enganoso: só a
transação mais externa realmente conta. Um COMMIT interno apenas
decrementa o contador (@@TRANCOUNT); só o COMMIT
que zera o contador grava de fato. Já um ROLLBACK, em qualquer nível,
desfaz tudo, até a mais externa.
BEGIN TRANSACTION;
UPDATE dbo.Conta SET Saldo = Saldo - 100 WHERE Id = 1;
SAVE TRANSACTION PontoIntermediario;
UPDATE dbo.Conta SET Saldo = Saldo + 100 WHERE Id = 999; -- conta não existe
IF @@ROWCOUNT = 0
ROLLBACK TRANSACTION PontoIntermediario; -- desfaz só o segundo UPDATE
COMMIT TRANSACTION; -- o débito da conta 1 é mantido
SAVE TRANSACTION cria um ponto de retorno parcial — útil, mas raro no
dia a dia. Na prática, a grande maioria das procedures usa uma única transação simples como no
exemplo da seção 3.
5. Onde isso te leva
Você já sabe blindar uma procedure contra falhas parciais. O que falta é entender
TRY/CATCH em profundidade — capturar o erro, ler os detalhes
(ERROR_MESSAGE(), ERROR_LINE()...) e decidir
o que relançar para a aplicação C#. É o próximo capítulo.