Localizar a fronteira antes de corrigir
O guião usa três ligações reais para um ficheiro SQLite temporário com dados inventados. Não existe pool nesta oficina: cada ligação já está disponível quando a instrução é chamada. A inicia BEGIN IMMEDIATE e altera o valor de 10 para 20 sem confirmar. A observa 20 e outra ligação continua a observar 10. Quando B tenta iniciar uma escrita, recebe SQLITE_BUSY, código 5. Depois de A reverter, B consegue escrever e confirmar 30. Regista a sequência, não apenas a mensagem. A evidência discrimina contenção entre escritores da falta de ligações livres estudada anteriormente. Num registo de problema, guarda também a operação afetada, a configuração de transações e a versão, para que outra equipa possa reproduzir a condição.
A mesma leitura pode permanecer antiga
Na segunda sequência, A inicia uma transação de leitura e consulta 30. B altera o valor para 40 e confirma. A continua a consultar 30, enquanto a terceira ligação vê 40. Ao tentar escrever, A recebe SQLITE_BUSY_SNAPSHOT, código 517. Repetir o UPDATE na mesma transação produz o mesmo erro. Esta observação evita duas correções sem fundamento: aumentar o pool e repetir indefinidamente a instrução. O guião termina a transação de A, inicia outra e relê 40. A intenção fictícia é acrescentar dez ao valor atual, pelo que confirma 50. Reutilizar o cálculo antigo de 30 mais dez teria um resultado diferente. Uma decisão aprovada sobre dados específicos pode precisar de nova validação antes de qualquer repetição.
Reservar cedo também tem custo
A terceira sequência obtém a posição de escritor antes de consultar. Enquanto A mantém BEGIN IMMEDIATE ativo, B não consegue iniciar a sua escrita, embora uma leitura noutra ligação funcione. A estratégia pode evitar a promoção tardia de uma leitura, mas desloca a contenção para o início e torna a duração da reserva relevante. Num cenário fictício de fecho, manter essa reserva durante uma chamada externa lenta pode impedir outros jobs de avançar. O PM técnico deve pedir a fronteira completa da unidade de trabalho, dependências e tempos observados. Um busy timeout maior pode absorver contenção transitória com mais espera; não renova a imagem antiga. A decisão precisa de orçamento temporal e resultado funcional, sem transformar o parâmetro num SLA universal.
Reprodução, hipótese e limites
Antes de executar, prevê o valor observado em cada ligação e o ponto em que cada escrita pode falhar. Compara a previsão com checks e trace no relatório JSON. O trace guarda instruções, parâmetros, resultados e códigos; o relatório identifica Python, SQLite e o hash do guião. As sequências são intercaladas num único thread para controlar a ordem, não para medir throughput. O laboratório fixa LEGACY_TRANSACTION_CONTROL e isolation_level=None e usa instruções SQL explícitas de transação. Essa escolha deve acompanhar a reprodução. Duas execuções iguais apoiam estes mecanismos locais; não constituem diagnóstico de um incidente real, ensaio de Oracle/JDBC, teste de rede, garantia de desempenho ou aprovação humana do procedimento de RUN.
python3 content/labs/problem-transactions/run.py --output /tmp/dr-problem-transactions.json
# Controlled sequence, separate connections, confirmed WAL mode:
# A: BEGIN; SELECT value ... -> 30
# B: BEGIN IMMEDIATE; UPDATE ... SET value=40; COMMIT
# A: UPDATE ... -> SQLITE_BUSY_SNAPSHOT (517)
# A: ROLLBACK; BEGIN IMMEDIATE; reread and reevaluate; COMMITA lê 30; B confirma 40; A continua a ler 30 e recebe 517 ao tentar escrever. Só uma nova transação permite reavaliar o incremento e confirmar 50.
Armadilhas comuns
Tratar qualquer bloqueio como pool esgotado, repetir sobre a mesma leitura antiga ou reutilizar um valor calculado antes da alteração concorrente.
Tópicos relacionados: Recursos e cancelamento · Atomicidade e aceitação de correções
Identifica onde a operação falha e qual estado conserva; renovar uma transação exige também reavaliar os pressupostos da operação.
Referência: Transaction · Problem management practices 2026-09; ServiceNow Brazil examples with scoped plugins and properties