Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

01 - Relacional (PostgreSQL, MySQL, SQL Server)

#acid#mysql#postgresql#sql#sql-server
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione 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 ANALYZE antes 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_activity para 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_replication no PostgreSQL, Seconds_Behind_Source no 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 COPY para cargas volumosas, ajustando maintenance_work_mem e 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.