Boas práticas de gestão de transações em operações críticas
1. Fundamentos de transações em sistemas críticos
Uma transação em banco de dados é uma unidade lógica de trabalho que deve ser executada de forma atômica, consistente, isolada e durável — as conhecidas propriedades ACID. Em operações críticas, como transferências financeiras, registro de doses de medicamentos ou atualização de estoque em logística, a violação de qualquer uma dessas propriedades pode gerar prejuízos financeiros, riscos à saúde ou falhas operacionais graves.
Transações curtas (milissegundos) minimizam contenção de locks, enquanto transações longas (segundos ou minutos) aumentam a probabilidade de deadlocks e degradação de performance. Uma operação é considerada crítica quando sua falha ou inconsistência pode causar danos irreversíveis — por exemplo, debitar o saldo de uma conta sem efetivar o crédito correspondente.
2. Escolha do nível de isolamento adequado
O nível de isolamento define como as transações enxergam modificações feitas por outras transações concorrentes. Os principais níveis e seus riscos:
- READ COMMITTED: evita leitura suja, mas permite leituras não repetíveis e fantasmas. Adequado para operações onde pequenas inconsistências são toleráveis.
- REPEATABLE READ: garante que leituras repetidas retornem os mesmos dados, mas ainda permite fantasmas. Indicado para relatórios financeiros.
- SERIALIZABLE: elimina todas as anomalias, mas reduz drasticamente a concorrência. Essencial em operações de alta criticidade, como reserva de assentos em voos.
No PostgreSQL, o nível padrão é READ COMMITTED. Para operações críticas, configure:
-- Sessão atual
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Transação específica
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- operações críticas
COMMIT;
No MySQL (InnoDB), o padrão é REPEATABLE READ. Para máxima consistência:
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
-- operações críticas
COMMIT;
3. Controle de concorrência e deadlocks
Existem duas abordagens principais:
Lock pessimista: bloqueia explicitamente os recursos antes de usá-los. Exemplo em PostgreSQL:
BEGIN;
SELECT * FROM contas WHERE id = 123 FOR UPDATE;
UPDATE contas SET saldo = saldo - 500 WHERE id = 123;
UPDATE contas SET saldo = saldo + 500 WHERE id = 456;
COMMIT;
Lock otimista: usa versões de linha (row versioning) e verifica conflitos no commit. Exemplo com número de versão:
BEGIN;
SELECT saldo, versao FROM contas WHERE id = 123;
-- aplicação verifica versao
UPDATE contas SET saldo = saldo - 500, versao = versao + 1
WHERE id = 123 AND versao = 10;
-- se linhas afetadas = 0, conflito detectado
COMMIT;
Para evitar deadlocks:
- Acesse recursos sempre na mesma ordem (ex: menor ID primeiro)
- Defina timeouts razoáveis: SET lock_timeout = '5s';
- Monitore deadlocks com pg_stat_database e logs do PostgreSQL
4. Gestão de erros e rollback em transações
A estrutura básica de tratamento deve garantir rollback em caso de falha:
BEGIN;
SAVEPOINT antes_operacao1;
UPDATE contas SET saldo = saldo - 1000 WHERE id = 1;
-- se falhar:
ROLLBACK TO SAVEPOINT antes_operacao1;
SAVEPOINT antes_operacao2;
UPDATE contas SET saldo = saldo + 1000 WHERE id = 2;
-- se falhar:
ROLLBACK TO SAVEPOINT antes_operacao2;
-- se tudo ok:
RELEASE SAVEPOINT antes_operacao1;
RELEASE SAVEPOINT antes_operacao2;
COMMIT;
Para auditoria, registre logs de transação:
CREATE TABLE log_transacoes (
id SERIAL PRIMARY KEY,
operacao TEXT,
dados_antes JSONB,
dados_depois JSONB,
usuario TEXT,
timestamp TIMESTAMPTZ DEFAULT NOW()
);
5. Transações distribuídas e consistência eventual
Em sistemas distribuídos, o two-phase commit (2PC) oferece consistência forte, mas com latência e risco de bloqueio. Alternativas incluem:
Saga pattern: divide a transação longa em etapas com compensações:
-- Etapa 1: reservar estoque
BEGIN;
UPDATE estoque SET quantidade = quantidade - 1 WHERE produto_id = 10;
COMMIT;
-- Se etapa 2 falhar:
BEGIN;
UPDATE estoque SET quantidade = quantidade + 1 WHERE produto_id = 10;
COMMIT;
Para alta disponibilidade, considere BASE (Basically Available, Soft state, Eventual consistency) em vez de ACID, aceitando inconsistências temporárias em troca de resiliência.
6. Performance e escalabilidade em transações críticas
Para minimizar contenção:
- Mova operações não críticas para fora da transação (ex: envio de email após commit)
- Use batch processing para atualizações em lote
- Otimize índices:
CREATE INDEX idx_contas_saldo ON contas(saldo) WHERE ativo = true; - Configure pool de conexões com
pgbouncerouHikariCP
Exemplo de query otimizada:
-- Ruim (varredura completa)
UPDATE contas SET saldo = saldo - 100 WHERE id = 123 AND saldo >= 100;
-- Bom (usa índice)
BEGIN;
SELECT saldo FROM contas WHERE id = 123 FOR UPDATE;
-- verificação em código
UPDATE contas SET saldo = saldo - 100 WHERE id = 123;
COMMIT;
7. Testes e validação de transações em produção
Simule concorrência com pgbench:
pgbench -c 10 -j 2 -T 60 -f transacao_critica.sql -n -P 5 meu_banco
Monitore métricas essenciais:
SELECT
datname,
xact_commit,
xact_rollback,
deadlocks,
conflicts
FROM pg_stat_database;
Implemente canary releases com feature flags:
-- Feature flag para novo fluxo de transação
DO $$
BEGIN
IF current_setting('app.feature_novo_fluxo') = 'true' THEN
-- novo fluxo de transação
ELSE
-- fluxo legado
END IF;
END $$;
8. Documentação e governança de transações
Política de retry com backoff exponencial:
-- Tenta até 3 vezes com espera crescente
DO $$
DECLARE
tentativa INTEGER := 0;
max_tentativas INTEGER := 3;
espera_base INTEGER := 1; -- segundos
BEGIN
LOOP
BEGIN
-- transação crítica
COMMIT;
EXIT;
EXCEPTION
WHEN serialization_failure THEN
tentativa := tentativa + 1;
IF tentativa >= max_tentativas THEN
RAISE;
END IF;
PERFORM pg_sleep(espera_base * tentativa);
END;
END LOOP;
END $$;
Padrões de nomenclatura:
-- Prefixo: tx_ para transações, sp_ para stored procedures
CREATE OR REPLACE PROCEDURE sp_transferencia_entre_contas(
p_conta_origem INT,
p_conta_destino INT,
p_valor NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
-- corpo da transação
END;
$$;
Revise planos de execução periodicamente:
EXPLAIN (ANALYZE, BUFFERS)
UPDATE contas SET saldo = saldo - 100 WHERE id = 123;
SELECT * FROM pg_locks WHERE NOT granted;
Referências
- PostgreSQL Documentation: Transaction Isolation — Documentação oficial sobre níveis de isolamento e controle de concorrência no PostgreSQL.
- MySQL InnoDB Transaction Model — Guia completo do modelo transacional do InnoDB, incluindo locks e deadlocks.
- Martin Fowler: Saga Pattern — Artigo técnico sobre o padrão Saga para transações distribuídas.
- PostgreSQL Wiki: Lock Monitoring — Tutoriais e scripts para monitoramento de locks e deadlocks em produção.
- Microsoft: Transaction Management Best Practices — Boas práticas de gerenciamento de transações em sistemas críticos, com exemplos práticos.
- AWS: Best Practices for Transactional Databases — Guia da AWS sobre otimização de transações em ambientes cloud.
- Percona: Transaction Isolation Levels Explained — Explicação detalhada dos níveis de isolamento com exemplos práticos.