ChaosSQL

Catálogo de Concorrência

Cenários Canônicos & Mitigações

Explore 10 cenários de falhas de concorrência baseados em pesquisas acadêmicas de isolamento e casos reais de sistemas financeiros e distribuídos.

P4

Lost Update Bancário

Saques concorrentes sob READ COMMITTED sobrescrevem saldos sem serialização transacional, perdendo quantias silenciosamente.

Saques concorrentes sob READ COMMITTED sobrescrevem saldos sem serialização transacional, perdendo quantias silenciosamente.

Schema e dados iniciais

schema.sql & seed.sql
-- Schema
CREATE TABLE accounts (
    id INT PRIMARY KEY,
    owner TEXT NOT NULL,
    balance INT NOT NULL
);

-- Seed
INSERT INTO accounts VALUES (1, 'Alice', 1000);

Carga concorrente (chaos.yaml)

chaos.yaml
version: "1.0"
name: "banking_lost_update"
database:
  driver: "sqlite"
operations:
  - name: "withdraw_100"
    steps:
      - "SELECT balance FROM accounts WHERE id = 1 -> cur"
      - "UPDATE accounts SET balance = {cur - 100} WHERE id = 1"
invariants:
  - name: "total_balance_check"
    query: "SELECT balance FROM accounts WHERE id = 1;"
    assert: "balance == 1000 - (total_completed * 100)"

Por que a invariante quebra

Duas transações concorrentes de leitura-modificação-escrita leem o saldo de 1000 simultaneamente. T1 escreve 900, mas T2 sobrescreve com 900 com base em sua leitura defasada, perdendo silenciosamente $100.

T1 ──(rw)──► T2 ──(ww)──► T1

Delta debugging reduz 20 operações às 2 que causam a falha.

Correção em produção

Update Atômico com Predicado de Guarda ou Bloqueio Pessimista

A anomalia ocorre pelo padrão vulnerável Read-Modify-Write sob READ COMMITTED: a aplicação lê o valor em um SELECT e computa a subtração na memória do servidor web. Se duas goroutines lerem balance=1000 ao mesmo tempo, ambas calculam 900 e o último UPDATE sobrescreve o primeiro. A mitigação transfere o cálculo para o motor de banco em uma única instrução atômica (UPDATE accounts SET balance = balance - 100 WHERE id = 1 AND balance >= 100), serializando a alteração sob o bloqueio exclusivo de linha.

✓ PostgreSQL✓ SQLite (WAL)✓ MySQL (InnoDB)

PostgreSQL, SQLite (WAL), MySQL (InnoDB)

remediation_fix.sql
-- Opção 1: Update Atômico com Aritmética no Motor (Recomendado)
UPDATE accounts 
SET balance = balance - 100 
WHERE id = 1 AND balance >= 100;

-- Opção 2: Bloqueio Pessimista dentro de Transação Explícita
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- Aplicação valida balance >= 100 antes de submeter escrita:
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;