# 🗄️ Padrões de Banco de Dados (O Protocolo "Integridade Relacional") **Público:** Agentes de Backend & Arquitetos. **Objetivo:** Construir schemas escaláveis e compatíveis que preferem PostgreSQL, mas aceitam limitações do MySQL. > [!CRITICAL] > **O Mandato dos Dados:** > "Código é temporário. Dados são permanentes. Schemas quebrados são uma sentença perpétua." ## 1. 🏗️ Arquitetura & Stack ### A Camada de Abstração - **ORM Obrigatório:** Use **SQLAlchemy (Async)** ou **Prisma** (se Node). - **Justificativa:** Precisamos alternar entre Postgres e MySQL sem reescrever queries. SQL puro é proibido, a menos que seja para relatórios otimizados específicos. - **Migrações:** **Alembic** (Python) ou **Prisma Migrate**. - *Regra:* Nunca modifique o DB manualmente. Code-first sempre. ### O Duelo: PostgreSQL vs MySQL Preferimos **PostgreSQL**. * **Por que:** JSONB, Melhor Indexação, Confiabilidade. * **Suporte MySQL:** Devemos suportá-lo, então evite lógica que dependa *exclusivamente* de extensões obscuras do Postgres, a menos que esteja por trás de uma feature flag. ## 2. 🏛️ Regras de Design de Schema ### Convenções de Nomenclatura (Snake_Case) - **Tabelas:** Plural, snake_case (`users`, `order_items`, `audit_logs`). - **Colunas:** Singular, snake_case (`created_at`, `user_id`, `is_active`). - **Chaves:** - Primária: `id` (UUIDv7 ou BigInt otimizado). - Estrangeira: `target_id` (ex: `user_id` referenciando `users.id`). ### Disciplinas de Tipo - **Timestamps:** SEMPRE use `UTC`. - Coluna: `created_at` (TIMESTAMP WITH TIME ZONE). - Coluna: `updated_at` (Trigger de auto-update). - **JSON:** Use `JSONB` (Postgres) / `JSON` (MySQL). - *Restrição:* Não trate o DB como um document store. Use JSON apenas para metadados variáveis, não para relações principais. - **Booleans:** Use `BOOLEAN`. (MySQL define como TinyInt(1) automaticamente, o ORM lida com isso). ## 3. 🛡️ Performance & Confiabilidade ### Estratégia de Indexação - **Chaves Estrangeiras:** DEVEM ser indexadas. - **Busca:** Se buscar texto, use Trigram (Postgres) ou FullText (MySQL). - **Unicidade:** Force no nível do DB (`unique=True`), não apenas no nível do código. ### O Pecado "N+1" - **Eager Loading:** Agentes devem fazer join explícito de tabelas (`select_related` / `joinedload`). - **Paginação:** NUNCA retorne `SELECT *` sem `LIMIT/OFFSET` (Paginação por cursor preferida para grandes conjuntos). ## 4. 🔒 Checklist de Compatibilidade (Postgres vs MySQL) Antes de commitar uma migração, verifique: 1. **Quoting:** Postgres usa aspas duplas `"tabela"`, MySQL usa crases `` `tabela` ``. *Resultado: Use o ORM para lidar com isso.* 2. **Case Sensitivity:** MySQL no Windows é case-insensitive. Postgres é case-sensitive. *Resultado: Mantenha snake_case minúsculo explicitamente.* 3. **Enums:** ENUMs nativos são complicados em migrações. *Resultado: Use colunas VARCHAR com validação de Enum no nível da Aplicação OU tabelas de lookup.* ## 5. 🤖 A Auditoria "Auto-Query" do Agente "Antes de eu executar esta query/migração..." - [ ] Eu usei um arquivo de migração? - [ ] O padrão de `created_at` está definido como `now()`? - [ ] Estou buscando 10.000 linhas? (Adicione LIMIT). - [ ] Se eu deletar um Pai, o que acontece com o Filho? (Defina `ON DELETE CASCADE` ou `SET NULL`). ## 6. ⏱️ Autodiagnóstico de Performance (A Checagem "Slow Query") Agentes devem rodar estas checagens mentais ou reais em qualquer query complexa: ### Teste A: O Ritual "Explain" Antes de finalizar uma query, simule `EXPLAIN` (Postgres) ou `EXPLAIN ANALYZE`. * **Condição de Falha:** O resultado mostra `Seq Scan` em uma tabela com > 1000 linhas? * **Correção:** Adicione um index na coluna filtrada (`WHERE coluna = ...`). ### Teste B: A Armadilha "Sem Limites" * **Condição de Falha:** Uma query sem lógica de `LIMIT` ou `PAGE_SIZE`. * **Correção:** Injete forçadamente `LIMIT 100` durante dev/teste para verificar. ### Teste C: O Detector "N+1" * **Condição de Falha:** Usar um loop para buscar dados relacionados. ```python users = session.query(User).all() for user in users: print(user.address) # 🚨 RUIM: Uma query por usuário ``` * **Correção:** Use Eager Loading. ```python users = session.query(User).options(joinedload(User.address)).all() # ✅ BOM: Único JOIN ```