90 lines
4.4 KiB
Markdown
90 lines
4.4 KiB
Markdown
# 🗄️ 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
|
|
```
|