📌 Material de chegada: caderno de bordo com 1 caso público de race + 1 dúvida sobre isolamento + cliente SQL pronto para 2 sessões. Esses 3 itens alimentam o daily de abertura.
Bloco curto e denso de fundamentos: por que transações existem, ACID, 4 níveis de isolamento, BEGIN/COMMIT/ROLLBACK, locks pessimista vs otimista, anatomia de triggers.
Em grupo, no repositório do projeto: sp_comprar_ingresso + trg_avaliacao_audit + teste manual de race condition. Continua na janela de dev (16h–18h).
avaliacao🎯 Saída do dia: Merge Request feat(db): transactions + triggers de auditoria aberto no repositório do grupo, com README.md documentando o teste de race condition (com lock vs sem lock). Sem nota ponderada — entrega coletiva avaliada pelo professor.
SELECT qtd FROM estoque
WHERE id = 42SELECT qtd FROM estoque
WHERE id = 42UPDATE estoque
SET qtd = 0INSERT INTO ingresso ...UPDATE estoque
SET qtd = 0 (já era 0)INSERT INTO ingresso ...Entre o SELECT e o UPDATE da T1, a T2 leu o mesmo valor desatualizado. O banco não sabia que essas duas operações eram um único negócio.
Empacotar o par SELECT-then-UPDATE numa transação atômica com lock. Esse é o tema da aula. Vamos da teoria (ACID) ao código (TRY/CATCH).
INSERT ingresso falhou e ficou assim.qtd ficou negativo, ou ingresso aponta para usuário inexistente.COMMIT, o dado sobrevive a queda de energia, crash, restart.📌 Foco do dia: os 3 primeiros — A, C e I — são onde você como dev escreve código. Durability é responsabilidade do SGBD (WAL, fsync). Hoje, mire em A + C + I.
| Nível ↓ / Anomalia → | Dirty Read 💀 Dirty Read Você lê dados que outra transação modificou mas ainda não confirmou (sem Se a outra transação fizer Ex.: Tx A atualiza saldo para R$ 100, ainda sem COMMIT. Tx B lê R$ 100 e age sobre isso. Tx A faz ROLLBACK. Tx B agiu com um dado fantasma. |
Non-repeatable Read 🔁 Non-repeatable Read Dentro da mesma transação, você lê o mesmo registro duas vezes e obtém valores diferentes. Outra transação fez Ex.: Tx A lê o preço do produto = R$ 50 às 10h00. Tx B atualiza o preço para R$ 65 e comita. Tx A lê de novo às 10h01 e recebe R$ 65 — mesma transação, leitura inconsistente. |
Phantom Read 👻 Phantom Read Você roda a mesma query com Outra transação fez Ex.: Tx A faz SELECT * FROM pedidos WHERE status='ABERTO' e recebe 12 linhas. Tx B insere mais 1 pedido aberto e comita. Tx A repete o SELECT e recebe 13 — apareceu um fantasma. |
Serialization Anomaly 🌀 Serialization Anomaly Duas transações concorrentes produzem juntas um resultado final que não corresponde a nenhuma ordem serial possível de execução delas — viola a ilusão de "uma de cada vez". É mais sutil que as outras: cada Tx, em isolamento, parece OK; o conjunto é que é impossível. Ex.: Tx A e Tx B leem que há 2 médicos de plantão. Cada uma decide "posso me retirar, ainda sobra 1". As duas comitam. Resultado: zero médicos — estado impossível em qualquer ordem serial. |
|---|---|---|---|---|
| READ UNCOMMITTED | permite | permite | permite | permite |
| READ COMMITTED (default PG/SQL Server) | bloqueia | permite | permite | permite |
| REPEATABLE READ | bloqueia | bloqueia | permite* | permite |
| SERIALIZABLE | bloqueia | bloqueia | bloqueia | bloqueia |
Leio dado que outra tx mexeu mas não comitou ainda.
Leio o mesmo registro 2x na mesma tx, valor mudou.
Mesmo WHERE, 2x, surgem novos registros.
Resultado é impossível em qualquer ordem serial real.
* PostgreSQL bloqueia phantom em REPEATABLE READ via MVCC; SQL Server não.
📌 Por que esse padrão? O WHERE qtd > 0 + @@ROWCOUNT rejeitam venda quando estoque é zero sem precisar de SELECT prévio. O THROW dispara o CATCH, que dá ROLLBACK. Ainda falta o lock — vem no próximo slide.
Trava o registro quando lê. Outras transações esperam.
Não trava. Confere no UPDATE que ninguém mexeu antes via versão ou timestamp.
📌 Heurística rápida: ingresso, saldo, estoque crítico → pessimista. Comentário, like, perfil de usuário → otimista com versão. Nunca use otimista quando o custo de "perdeu a corrida" é dinheiro real.
CHECK não cobreBEFORE — antes da operação (PG)AFTER — depois da operação (padrão)INSTEAD OF — substitui a operação (em views)INSERT, UPDATE, DELETE (ou combinações)insertedTabela virtual com as linhas novas de INSERT/UPDATE.
deletedTabela virtual com as linhas antigas de UPDATE/DELETE.
UPDATE = DELETE + INSERT logicamente — você tem o "antes" e o "depois".
SET NOCOUNT ON — evita confundir o app com counts extrasinserted/deleted podem ter N linhas, nunca assuma 1📌 Regra prática: trigger é ótimo para auditoria simples e invariantes do banco. Para lógica de negócio e integrações, é dívida técnica esperando para acontecer.
BEGIN · lock · INSERT · COMMIT / ROLLBACK200 OK · 409 sold_out · 500 erroidle · loading · success · sold_out · errorRAISE EXCEPTION 'sold_out' → API: 409 → Repo: { ok:false, reason:'sold_out' } → ViewModel vira sold_out → View: modal "ingresso esgotado".ROLLBACK automático → API: 500 → Repo: 'deadlock' → ViewModel: error → View: "tente novamente".ingresso_id → API: 200 + JSON → Repo: { ok:true, ingressoId } → ViewModel: success → View: toast verde + navegação.📌 A grande sacada: a procedure não decide nada visual — ela só fala o que aconteceu via códigos. Cada camada acima traduz esse "o quê" para o vocabulário dela. View nunca toca SQL. Procedure nunca toca pixel.
📌 Por que union: o TypeScript te força a tratar cada caso. Esqueceu 'deadlock'? Erro de compilação. Esse é o jeito de evitar bug em produção que só aparece quando o backend cospe um erro novo.
📌 Olhe a divisão de trabalho: Repository só fala HTTP. Sabe que 409 = "sold_out". Não sabe quem é o usuário, não sabe que tela está aberta. Domínio puro. É o que torna esse código testável sem precisar montar tela nem banco.
📌 Por que reducer e não useState: o estado tem 5 formas mutuamente exclusivas. Booleanos soltos (isLoading, hasError, isSoldOut) permitem combinações impossíveis ("loading + sold_out"). Reducer + union type tornam estados ilegais impossíveis de existir.
idleBotão "Comprar" habilitado. Nada mais visível. Estado inicial e pós-reset.
loadingBotão "Comprando..." + ActivityIndicator. Desabilita o tap pra evitar double-click (compra duplicada no banco).
successToast verde com ingressoId. Você pode também navegar para a tela "Meus ingressos" aqui.
sold_outModal bloqueante. UX honesta: o último ingresso era exatamente esse. reset volta a idle.
errorAlert com mensagem técnica. Deadlock entra aqui — sugere "tente novamente" (o lock pessimista resolve no retry).
📌 O ciclo: toca → loading → resposta → reducer escolhe 1 dos 4 estados terminais → JSX re-renderiza. Tudo isso nasceu do contrato da sua sp_comprar_ingresso.
App em React Native + Expo com UMA tela: input para escrever uma avaliação, botão "Enviar" e lista das últimas 5 avaliações cadastradas.
Sem login. Estilo livre. Foco no fluxo: digitar → salvar → ver na lista.
Criar projeto no Supabase, tabela avaliacao(id, texto, criado_em) e tabela log_avaliacao(id, op, payload, criado_em).
App usa @supabase/supabase-js com URL + anon key.
trg_avaliacao_log em AFTER INSERT na tabela avaliacao que grava automaticamente uma linha em log_avaliacao.
App nunca escreve no log; quem escreve é o trigger.
Rodar npx expo start, abrir no celular via Expo Go (QR code) ou no emulador Android/iOS — demonstrar tela funcionando.
⭐ Excelência: exibir também a lista de logs lida diretamente da tabela log_avaliacao.
📌 Fluxo de entrega (em grupo):
feat/mobile-supabase-trigger no repositório do grupoapp/ com o projeto Expo (npx create-expo-app) e db/ com schema.sql + trg_avaliacao_log.sqlREADME.md com print da tela rodando no Expo Go e print da tabela log_avaliacao populada pelo triggerfeat(mobile): tela de avaliacao + supabase + trigger de log⚠️ Importante: sem nota ponderada. A entrega coletiva é avaliada qualitativamente — vale como débito técnico zero para a próxima aula (que aprofunda mobile, MVVM e integração com a API).
feat/db-transactions-triggerslog_auditoria e log_avaliacao_auditsp_comprar_ingresso com lock pessimistatrg_avaliacao_audit com inserted/deletedSERIALIZABLE + análise throughputBEGIN/COMMIT/ROLLBACK em TRY/CATCH