🔄 CAPÍTULO 03: TRANSAÇÕES ACID, NUVEM E CONCORRÊNCIA


🎯 Objetivos de Aprendizagem

Ao final deste capítulo (estimativa: 2 horas de estudo autoguiado), você será capaz de:

  • 🔹 Dominar os 4 pilares transacionais ACID: Atomicidade (Tudo ou Nada), Consistência (Regras Válidas), Isolamento (Sem Interferência) e Durabilidade (Gravado em Disco).
  • 🔹 Analisar os 4 Níveis de Isolamento ANSI SQL: Read Uncommitted, Read Committed, Repeatable Read e Serializable.
  • 🔹 Compreender o mecanismo de Write-Ahead Logging (WAL) e recuperação de falhas em SGBDs.
  • 🔹 Implementar transações seguras em Python utilizando session.begin(), session.commit() e session.rollback().

No coração de sistemas críticos (Bancos, Hospitais, Logística), existe o conceito de Transação. Uma transação não é apenas um comando SQL solto, mas uma Unidade Lógica de Trabalho indestrutível. 🛡️🧩

🏢 O Cenário Prático (Seu Desafio)

Você atua na TecProExpress. Imagine que o sistema debita R$ 1.500 do saldo de um cliente para pagar um frete internacional, mas antes do sistema creditar esse valor na conta da TecProExpress, o servidor de banco de dados sofre uma queda de energia.

"Seu desafio é arquitetar o banco de dados para garantir que, caso o fluxo inteiro não se complete, o dinheiro volte imediatamente para a conta do cliente (Rollback). Nenhum centavo pode desaparecer do sistema."


🧠 Fundamentos: O Escudo de Confiança (ACID)

Para garantir que o seu banco nunca entre em um estado corrupto, o SGBD aplica as propriedades ACID:

  1. ⚛️ Atomicidade (Tudo ou Nada): Se uma transação tem 10 passos e o passo 9 falha, os 8 anteriores são desfeitos automaticamente.
  2. ⚖️ Consistência (Regras): A transação leva o banco de um estado válido para outro estado válido (ex: o saldo nunca ficará negativo se houver um CHECK >= 0).
  3. 🔒 Isolamento (Invisibilidade): Se o operador A e o operador B estiverem alterando o estoque ao mesmo tempo, um não enxerga os dados parciais do outro.
  4. 💾 Durabilidade (Permanência): Após o comando COMMIT, o dado está salvo no disco permanentemente. Nem mesmo um apagão remove o dado.

📊 Fluxo da Transação (TCL)

flowchart TD
    START["▶️ BEGIN TRANSACTION"] --> OP1["📝 UPDATE: Debitar Saldo Cliente"]
    OP1 --> OP2["📝 UPDATE: Creditar Conta TecProExpress"]
    OP2 --> DEC{"❓ Sucesso Total?"}
    DEC -- SIM --> COM["✅ COMMIT: Salva no Disco"]
    DEC -- NÃO --> ROL["❌ ROLLBACK: Desfaz Tudo"]
    COM --> END["🏁 Fim da Transação"]
    ROL --> END

Pilares ACID e Controle Transacional TCL


🔍 Detalhamento do Fluxo:

  • BEGIN TRANSACTION: Avisa ao banco que uma sequência de operações críticas começou.
  • COMMIT: A confirmação de que tudo deu certo.
  • ROLLBACK: O botão de "Pânico" ou "Ctrl+Z" que cancela todas as ações pendentes.

📖 Exemplo Guiado: Transações Seguras

Vamos simular o cenário da TecProExpress no banco de dados. Seguindo nossa regra de excelência: primeiro DDL, depois DML.

🛠️ Código do Exemplo

-- PASSO 1: DDL (Criar a conta corrente)
CREATE TABLE conta_corrente (
    id INT PRIMARY KEY,
    titular VARCHAR(100),
    saldo DECIMAL(10,2) CHECK (saldo >= 0)
);

-- PASSO 2: DML (Carga Inicial)
INSERT INTO conta_corrente VALUES (1, 'Cliente João', 2000.00);
INSERT INTO conta_corrente VALUES (2, 'TecProExpress', 0.00);

-- PASSO 3: O Fluxo Transacional (TCL + DML)
START TRANSACTION;
  -- Retira do Cliente
  UPDATE conta_corrente SET saldo = saldo - 1500.00 WHERE id = 1;
  -- Credita na Empresa
  UPDATE conta_corrente SET saldo = saldo + 1500.00 WHERE id = 2;
COMMIT;

🔍 Detalhamento do Código:

  • START TRANSACTION: Inicia a unidade lógica.
  • UPDATE: Altera os dados. O valor só será efetivado no banco de dados quando o COMMIT for lido e executado pelo sistema.

☁️ A Era da Nuvem (Cloud Databases)

Atualmente, não instalamos mais servidores físicos em salas geladas. Utilizamos a Cloud Computing para hospedar nossos SGBDs.

📊 Modelos de Nuvem

flowchart LR
    U1["👤 Usuário Final"]
    U2["🛠️ Dev / DBA"]
    U3["⚙️ SysAdmin / Infra"]
    
    subgraph Cloud ["Modelos de Serviços Cloud"]
    UC1(("SaaS: App Pronto"))
    UC2(("PaaS: DBaaS"))
    UC3(("IaaS: Infra Bruta"))
    end
    
    U1 --> UC1
    U2 --> UC2
    U3 --> UC3

🔍 Detalhamento dos Modelos:

  • IaaS (Infraestrutura como Serviço): Você aluga a máquina e tem que instalar o banco. Controle total, mas requer gestão manual.
  • PaaS (Plataforma como Serviço - Foco do DBA): O provedor (AWS, Azure) te entrega o banco pronto para conectar (DBaaS). Eles cuidam da atualização e do hardware.
  • SaaS (Software como Serviço): O usuário apenas usa o sistema (Ex: Netflix, Gmail).

🛠️ Prática Obrigatória: O Botão de Pânico

Cenário: Simular um erro transacional para a TecProExpress.

  1. Crie a tabela e os inserts da Prática Guiada acima.
  2. Inicie uma nova transação (START TRANSACTION).
  3. Tente transferir 3000.00 do Cliente João (que só tem 500 restantes). O banco dará um erro devido ao CHECK.
  4. Execute ROLLBACK;.

🏁 Resultado Esperado

Ao executar um SELECT * FROM conta_corrente, o saldo do Cliente João não deve ter sido negativado, comprovando que a transação bloqueou o estado inconsistente.



💻 Ponte Prática: Do SQL Manual ao SQLAlchemy 2.0 ORM

Como o Python e o SQLAlchemy 2.0 implementam o Controle Transacional ACID de forma blindada contra falhas?

🔴 1. A Abordagem com SQL Manual (Controle Transacional Manual)

No modelo manual, o desenvolvedor precisa lembrar de disparar rollback() no bloco except. Se esquecer, a conexão fica em estado inconsistente ou bloqueia o banco:

# ❌ ABORDAGEM COM SQL MANUAL: Risco de esquecer o rollback ou prender locks
import sqlite3

conn = sqlite3.connect("banco_legado.db")
cursor = conn.cursor()

try:
    cursor.execute("UPDATE contas SET saldo = saldo - 1500 WHERE id = 1;")
    cursor.execute("UPDATE contas SET saldo = saldo + 1500 WHERE id = 2;")
    conn.commit() # Se falhar antes daqui, o que acontece?
except Exception as err:
    conn.rollback() # Fácil de esquecer!
finally:
    conn.close()

🟢 2. A Abordagem com SQLAlchemy 2.0 (Gerenciador de Contexto session.begin())

Com o SQLAlchemy 2.0, usamos o gerenciador de contexto with session.begin():. A Atomicidade (A do ACID) é nativa: se qualquer linha do bloco levantar uma exceção, o rollback é executado instantaneamente:

# ✅ ABORDAGEM MODERNA COM SQLALCHEMY 2.0: Atomicidade e Rollback Automático
from sqlalchemy import create_engine, String, Float, CheckConstraint, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

class Base(DeclarativeBase):
    pass

class ContaCorrenteModel(Base):
    __tablename__ = "contas_correntes"
    __table_args__ = (
        CheckConstraint("saldo >= 0.0", name="chk_saldo_positivo"),
    )

    id: Mapped[int] = mapped_column(primary_key=True)
    titular: Mapped[str] = mapped_column(String(100), nullable=False)
    saldo: Mapped[float] = mapped_column(Float, nullable=False)

    def __repr__(self) -> str:
        return f"Conta({self.titular}) -> Saldo: R$ {self.saldo:.2f}"

🛠️ Mini-Projeto 03 (BD): Transferência Transacional Segura (ACID)

Objetivo: Simular um serviço de transferência financeira PIX entre contas e comprovar o rollback automático quando uma regra de negócio for violada.

📋 Pré-requisitos e Instalação

No terminal do seu ambiente virtual (PowerShell ou Bash), instale a biblioteca necessária:

pip install sqlalchemy

💻 Código Completo e Autocontido (miniprojeto_03_transacoes.py)

Crie o arquivo miniprojeto_03_transacoes.py e insira o código abaixo integralmente:

"""
Mini-Projeto 03: Transferência Transacional Segura (ACID)
Curso: GTI - Banco de Dados Relacionais e Engenharia de Software
Stack: Python 3.11+ | SQLAlchemy 2.0 | SQLite
"""
import os
from sqlalchemy import create_engine, String, Float, CheckConstraint, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

# 1. Definição Declarativa do Schema
class Base(DeclarativeBase):
    pass

class ContaCorrenteModel(Base):
    __tablename__ = "contas_correntes"
    __table_args__ = (
        CheckConstraint("saldo >= 0.0", name="chk_saldo_positivo"),
    )

    id: Mapped[int] = mapped_column(primary_key=True)
    titular: Mapped[str] = mapped_column(String(100), nullable=False)
    saldo: Mapped[float] = mapped_column(Float, nullable=False)

    def __repr__(self) -> str:
        return f"Conta({self.titular}) -> Saldo: R$ {self.saldo:.2f}"

# 2. Serviço com Controle Transacional ACID
class ServicoTransferencia:
    @staticmethod
    def transferir(engine, id_origem: int, id_destino: int, valor: float) -> bool:
        with Session(engine) as session:
            try:
                # with session.begin() abre a transação e faz COMMIT automático se tudo der certo
                with session.begin():
                    conta_origem = session.get(ContaCorrenteModel, id_origem)
                    conta_destino = session.get(ContaCorrenteModel, id_destino)

                    if not conta_origem or not conta_destino:
                        raise ValueError("Uma das contas informadas não existe!")

                    if conta_origem.saldo < valor:
                        raise ValueError(f"Saldo insuficiente na conta de {conta_origem.titular}!")

                    print(f"💸 Debitando R$ {valor:.2f} de {conta_origem.titular}...")
                    conta_origem.saldo -= valor

                    print(f"💰 Creditando R$ {valor:.2f} em {conta_destino.titular}...")
                    conta_destino.saldo += valor

                print("✅ [TCL] Transação confirmada com sucesso (COMMIT)!")
                return True
            except Exception as err:
                print(f"❌ [TCL] FALHA NA TRANSAÇÃO: {err}")
                print("🛡️ [ROLLBACK] Todas as operações foram revertidas pelo SGBD!")
                return False

# 3. Ponto de Entrada Executável
if __name__ == "__main__":
    DB_FILE = "tecpro_banco.db"

    # Reset preventivo para garantir idempotência em testes repetidos
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)

    engine = create_engine(f"sqlite:///{DB_FILE}", echo=False)
    Base.metadata.create_all(bind=engine)

    # Carga inicial de teste
    with Session(engine) as session:
        c1 = ContaCorrenteModel(id=1, titular="Cliente João", saldo=2000.0)
        c2 = ContaCorrenteModel(id=2, titular="TecProExpress Cargas", saldo=0.0)
        session.merge(c1)
        session.merge(c2)
        session.commit()

    print("🔐 SIMULAÇÃO DE TRANSAÇÕES ACID (TECPROEXPRESS):")
    print("=" * 60)

    # 1. Transferência Válida (R$ 1.500,00)
    ServicoTransferencia.transferir(engine, id_origem=1, id_destino=2, valor=1500.0)

    # 2. Transferência Inválida (Tentativa de transferir R$ 3.000,00 com saldo de apenas R$ 500,00)
    print("-" * 60)
    ServicoTransferencia.transferir(engine, id_origem=1, id_destino=2, valor=3000.0)

    # Consulta dos saldos finais:
    print("-" * 60)
    with Session(engine) as session:
        for conta in session.scalars(select(ContaCorrenteModel)).all():
            print(f"  📊 {conta}")
    print("=" * 60)

🚀 Como Executar

Execute o script no terminal:

python miniprojeto_03_transacoes.py

🖥️ Saída Esperada no Console

🔐 SIMULAÇÃO DE TRANSAÇÕES ACID (TECPROEXPRESS):
============================================================
💸 Debitando R$ 1500.00 de Cliente João...
💰 Creditando R$ 1500.00 em TecProExpress Cargas...
✅ [TCL] Transação confirmada com sucesso (COMMIT)!
------------------------------------------------------------
💸 Debitando R$ 3000.00 de Cliente João...
❌ [TCL] FALHA NA TRANSAÇÃO: Saldo insuficiente na conta de Cliente João!
🛡️ [ROLLBACK] Todas as operações foram revertidas pelo SGBD!
------------------------------------------------------------
  📊 Conta(Cliente João) -> Saldo: R$ 500.00
  📊 Conta(TecProExpress Cargas) -> Saldo: R$ 1500.00
============================================================

💡 Checkpoint de Lógica

Importante

Reflexão Profissional: Qual é a grande vantagem da nuvem em termos de transações financeiras? O DBaaS (Database as a Service) moderno oferece backups automáticos diários. Se um Rollback falhar por erro catastrófico, o provedor da nuvem pode restaurar o banco exatamente para 1 minuto antes do acidente. 🧠🛡️



🧪 Quiz de Fixação e Autoavaliação — Capítulo 03

1. Em uma transferência bancária de R$ 500, o sistema debita da Conta A, mas o servidor desliga antes de creditar na Conta B. Qual princípio ACID garante que o débito seja cancelado (desfeito) ao reiniciar?

  • A) Durabilidade
  • B) Atomicidade (Atomicity)
  • C) Isolamento
  • D) Autodescrição
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Atomicidade garante que a transação é indivisível: ou todas as operações têm sucesso (COMMIT) ou nenhuma é aplicada (ROLLBACK total).


2. O que é o fenômeno da 'Leitura Suja' (Dirty Read) em bancos de dados relacionais?

  • A) Quando o monitor do computador está empoeirado.
  • B) Quando uma transação lê dados que foram alterados por outra transação que AINDA NÃO fez commit (e que pode sofrer rollback em seguida).
  • C) Quando uma consulta SELECT demora mais de 10 segundos.
  • D) Quando a tabela não possui chave primária.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: Leituras sujas acontecem no nível de isolamento 'Read Uncommitted', lendo dados 'fantasmas' que podem nunca se concretizar no banco.


3. Qual é o papel do log de escrita prévia (Write-Ahead Logging - WAL) no PostgreSQL e SQLite?

  • A) Registrar o histórico de acessos dos usuários para fins de RH.
  • B) Garantir a Durabilidade: gravar as mudanças no arquivo de log sequencial em disco ANTES de alterar as páginas de dados na memória RAM, permitindo recuperação instantânea após quedas de energia.
  • C) Calcular a fatura mensal do serviço em nuvem.
  • D) Apagar tabelas antigas.
💡 Ver Resposta e Justificativa

Resposta Correta: B
Justificativa: O WAL garante que, mesmo se o servidor for desligado da tomada, o SGBD reconstrói todas as transações comitadas ao reiniciar lendo o log sequencial.


🎯 Laboratório Prático

Coloque este conhecimento em prática agora mesmo executando o roteiro autoguiado:
👉 ATIVIDADE 12: TRANSAÇÕES E ACID