01 - Relacional (PostgreSQL, MySQL, SQL Server)
video: https://youtu.be/... no frontmatter da nota.Relacional (PostgreSQL, MySQL, SQL Server)
O banco de dados relacional organiza dados em tabelas compostas por linhas e colunas, com relacionamentos entre tabelas garantidos por chaves estrangeiras e consultados por SQL declarativo. É o modelo mais maduro e consolidado da indústria, presente em praticamente todo sistema de produção desde os anos 1980. Sua maior promessa é o cumprimento das propriedades ACID, que garantem que transações sejam atômicas, consistentes, isoladas e duráveis. Em produção, isso significa que um pagamento debitado em uma conta aparece creditado na outra, mesmo diante de falha no meio da operação. Por essa razão, sistemas financeiros, de estoque, de cadastro e de faturamento quase sempre começam com um banco relacional. O PostgreSQL 18, lançado em 2025, renovou a relevância da categoria com I/O assíncrono, UUIDv7 nativo e suporte a OAuth 2.0.
Conceito
O modelo relacional representa cada entidade como uma tabela, onde cada linha é um registro único identificado por uma chave primária. As colunas definem o tipo e as restrições dos dados, como NOT NULL, UNIQUE e CHECK. Relacionamentos entre entidades são expressos por chaves estrangeiras, e o mecanismo de junção (JOIN) reconstrói visões complexas dos dados no momento da consulta. A linguagem SQL é declarativa: você descreve o resultado desejado, e não o algoritmo de leitura, delegando a decisão de execução ao otimizador. O esquema é definido antes da carga de dados, o que força um desenho cuidadoso e reduz a chance de inconsistências. A normalização evita redundância e anomalias de atualização, embora consultas intensas de leitura muitas vezes recorram a desnormalização controlada, com justificativa documentada.
Que problema resolve
Antes dos bancos relacionais, cada aplicação manipulava arquivos planos com lógica própria de gravação, busca e concorrência, gerando inconsistências difíceis de auditar. O modelo relacional resolve quatro problemas centrais de produção:
- Integridade referencial: o banco recusa um pedido que aponte para um cliente inexistente, sem depender da disciplina do código da aplicação.
- Concorrência: milhares de transações simultâneas leem e escrevem sem corromper dados uns dos outros, por meio de locks e controle multiversão.
- Atomicidade de operações compostas: várias escritas acontecem todas juntas ou nenhuma delas; não existe estado intermediário visível.
- Consultas imprevisíveis no futuro: SQL permite escrever novas perguntas sem reorganizar o armazenamento, o que dá longevidade ao modelo.
Em ambientes regulados, o log de transações (WAL no PostgreSQL, redo log no SQL Server, binlog no MySQL) ainda fornece trilha de auditoria e base para replicação e backup incremental. Esse mesmo log é a matéria-prima de pipelines de CDC (Change Data Capture) que sincronizam caches e índices de busca.
Funcionamento interno
Todo banco relacional de produção compartilha um conjunto de componentes internos semelhantes, ainda que com implementações distintas. Entender esses mecanismos ajuda a interpretar sintomas em produção, em vez de apenas reagir a eles.
- Log write-ahead: antes de alterar páginas de dados em disco, o banco grava a mudança no log sequencial; após um crash, a recuperação refaz operações confirmadas e desfaz as não confirmadas.
- Controle de concorrência: o PostgreSQL usa MVCC (controle multiversão), mantendo versões antigas de linhas até que o vacuum limpe as mortas; MySQL/InnoDB também usa MVCC com undo logs; o SQL Server combina locks com row versioning.
- Índices: estruturas majoritariamente B-tree, balanceadas, que transformam buscas lineares em buscas logarítmicas de custo O(log n).
- Otimizador de consultas: reescreve a consulta, estima custos com base em estatísticas das tabelas e escolhe entre varreduras, junções por hash, merge ou laço aninhado.
O PostgreSQL 18 introduziu I/O assíncrono nativo (io_method = worker ou io_uring), que empurra leituras em paralelo ao sistema operacional e traz ganhos relatados de 2 a 3 vezes em sequential scans, além de acelerar vacuum e leituras de índice bitmap. Em clusters de leitura intensa, isso se traduz em menos réplicas para a mesma carga.
-- PostgreSQL 18: tabela financeira com UUIDv7 nativo
-- UUIDv7 carrega o timestamp de milissegundos nos bits superiores,
-- o que melhora a localidade de inserção em índices B-tree
CREATE TABLE pagamentos (
id uuid PRIMARY KEY DEFAULT uuidv7(),
cliente_id bigint NOT NULL REFERENCES clientes(id),
valor numeric(12,2) NOT NULL CHECK (valor > 0),
status varchar(20) NOT NULL DEFAULT 'pendente',
criado_em timestamptz NOT NULL DEFAULT now()
);
-- Índice parcial: só cobre pagamentos pendentes, menor e mais rápido
CREATE INDEX idx_pagamentos_pendentes
ON pagamentos (criado_em)
WHERE status = 'pendente';
-- Consulta agregada; o I/O assíncrono do 18 acelera a varredura grande
SELECT status, count(*), sum(valor)
FROM pagamentos
WHERE criado_em >= now() - interval '30 days'
GROUP BY status;
-- Transação com isolamento explícito para leitura estável
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT saldo FROM contas WHERE id = 42;
UPDATE contas SET saldo = saldo - 100
WHERE id = 42 AND saldo >= 100;
COMMIT;
Transações e níveis de isolamento
O nível de isolamento define quais anomalias de concorrência uma transação tolera, e a escolha é uma decisão de arquitetura, não um detalhe. O padrão em quase todos os motores é READ COMMITTED, suficiente para a maioria dos fluxos web, mas insuficiente para relatórios que exigem visão estável ou para regras de negócio sensíveis a leituras duplicadas.
| Nível | Leitura suja | Leitura fantasma | Anomalia de escrita | Uso típico |
|---|---|---|---|---|
| READ UNCOMMITTED | Possível | Possível | Possível | Raro; só estimativas grosseiras |
| READ COMMITTED | Impossível | Possível | Possível | Padrão OLTP de aplicações web |
| REPEATABLE READ | Impossível | Impedida | Possível (variantes por motor) | Relatórios com visão estável |
| SERIALIZABLE | Impossível | Impedida | Impedida | Regras financeiras críticas |
No PostgreSQL, SERIALIZABLE usa SSI (Serializable Snapshot Isolation) e pode abortar transações com erro de serialização, o que exige política de retry no código. No SQL Server, o equivalente histórico exige hints de lock, embora o modo snapshot resolva a maior parte dos casos. A regra prática é adotar o nível mais fraco que satisfaça a invariante do negócio, pois quanto mais forte o isolamento, maior o custo de concorrência.
Aplicação prática
Em produção, a disciplina operacional determina o sucesso do relacional tanto quanto o desenho do esquema. Estes pontos concentram a maior parte dos problemas observados:
- Migrações versionadas: definições de esquema vivem em migrações revisadas (Flyway, Alembic, golang-migrate), nunca aplicadas manualmente; mudanças seguem expand-contract para evitar downtime.
- Planos de execução: consultas críticas são validadas com
EXPLAIN ANALYZEantes de irem para produção, e alertas disparam para regressões de plano. - Réplicas de leitura: consultas analíticas e de relatório vão para réplicas, enquanto a primária concentra escritas; a defasagem de replicação é monitorada e respeitada no roteamento.
- Pooling de conexões: conexões são reutilizadas por pool, tema coberto em detalhe nos arquivos deste módulo dedicados a connection pooling.
- Backup e restauração: a estratégia segue o RPO definido pelo negócio, com testes periódicos de restauração em ambiente isolado.
- Autenticação moderna: o PostgreSQL 18 aceita tokens OAuth 2.0 como mecanismo de autenticação, integrando identidade corporativa sem senhas persistentes no
pg_hba.conf.
Comparação entre os três motores
| Critério | PostgreSQL | MySQL 8 (InnoDB) | SQL Server |
|---|---|---|---|
| Licença | PostgreSQL License (open source) | GPL dual (Community e comercial) | Proprietária, cobrada por núcleo |
| Tipos de dados | Ricos: JSONB, arrays, ranges, GIS | Bons: JSON, espaciais básicos | Bons: JSON, hierarchyid, GIS |
| Concorrência | MVCC com vacuum | MVCC com undo log | Locks com row versioning |
| Extensibilidade | Alta: extensões (PostGIS, pgvector) | Baixa: plugins limitados | Média: CLR, SSIS e SSRS integrados |
| Replicação | Lógica e física | Binlog (row e statement) | Always On e log shipping |
| Ecossistema de nuvem | RDS, Aurora, Cloud SQL, Azure | RDS, Aurora, Cloud SQL | Azure SQL, EC2, containers |
| Ponto forte | Cobertura de recursos e padrão SQL | Simplicidade em cargas web | Integração com o mundo Microsoft |
PostgreSQL é frequentemente a escolha padrão de novas arquiteturas por cobertura de recursos, extensões (como pgvector para busca vetorial) e licença permissiva. MySQL segue forte em aplicações web simples, com ferramental conhecido e comportamento previsível em leitura massiva. SQL Server domina em organizações que já mantêm Active Directory, Power BI e aplicações .NET, oferecendo ferramentas de gestão como SSMS e Reporting Services. A tabela resume diferenças estruturais, mas a decisão final depende do time, do custo de licença e dos requisitos de consistência e disponibilidade.
Erros comuns
A maior parte dos incidentes em bancos relacionais não vem de falhas exóticas, e sim da repetição de erros já conhecidos e evitáveis:
- Índices criados por intuição sem
EXPLAIN ANALYZE, resultando em planos ineficientes e custo extra de escrita em cada insert; cada índice é um custo de manutenção contínua. - Transações longas que retêm locks e bloqueiam o vacuum, inflando tabelas e causando bloat; monitore
pg_stat_activitypara transações acima de alguns minutos. - Nível de isolamento mantido no padrão sem análise, produzindo leituras fantasma ou anomalias de escrita em fluxos financeiros.
- Chaves primárias UUIDv4 aleatórias, que fragmentam índices B-tree por falta de localidade e degradam inserções em tabelas grandes.
- Dependência de hints de otimizador não portáveis entre motores, acoplando a aplicação a um fornecedor específico.
- Ignorar o vacuum no PostgreSQL, acumulando tuplas mortas e degradando planos por estatísticas obsoletas.
- Falta de testes de restauração, que transforma backups inúteis em surpresa descoberta apenas no dia do incidente.
- Cargas em lote executadas linha a linha via INSERT unitário, desperdiçando ganhos de batch e prolongando janelas de carga.
Boas práticas
- Versione todo o esquema em migrações revisadas por pares, com rollback planejado e estratégia expand-contract para mudanças sem downtime.
- Trate o banco como fronteira de consistência: agregados pequenos, transações curtas e unicidade garantida por constraint, não apenas por código de aplicação.
- Configure réplicas e monitore defasagem (
pg_stat_replicationno PostgreSQL,Seconds_Behind_Sourceno MySQL), roteando leituras tolerantes à réplica. - Padronize níveis de isolamento por serviço e documente as exceções; leituras repetíveis para relatórios, read committed para fluxos transacionais comuns.
- Prefira UUIDv7 ou identidade incremental quando a chave for física; reserve identificadores aleatórios para exposição externa, se necessário.
- Mantenha estatísticas atualizadas (
ANALYZE) e automatize verificações de bloat e de índices não utilizados em janelas de manutenção. - Para cargas analíticas pesadas, separe do OLTP com materialized views, réplicas dedicadas ou data warehouse, preservando a primária para escrita transacional.
- Documente limites operacionais (tamanho de tabela, duração de transação, número de conexões) como contrato de arquitetura e alerte antes de estourá-los.
- Prefira INSERT em lote e
COPYpara cargas volumosas, ajustandomaintenance_work_meme desativando índices não essenciais durante a carga. - Separe responsabilidades por papel de banco: usuário de aplicação sem direitos de DDL, usuário de migração restrito a deploys automatizados.
- Meça o custo real de cada consulta crítica em p99 e registre o plano de execução aprovado, criando linha de base para detectar regressões.