O modelo define o teto de performance

Tunar consultas tem limite. Antes de discutir índice, plano de execução ou parâmetro de servidor, existe uma camada que decide o quanto o banco consegue entregar: o modelo de dados. Tipo de coluna, escolha de chave, granularidade das tabelas e nível de normalização determinam o custo de cada leitura e de cada escrita. Um índice criado sobre um modelo ruim apenas torna o problema um pouco mais lento de aparecer.

O ponto central é que o otimizador decide com base no que o modelo declara. Se a coluna de data é varchar, o filtro por período vira comparação de texto e o índice perde utilidade. Se não há chave estrangeira nem constraint, o planejador tem menos informação sobre cardinalidade e distribuição. Se status é texto livre, cada relatório precisa lidar com variações de grafia. Modelagem correta não é preciosismo acadêmico: é o que permite ao banco escolher um plano eficiente sem intervenção manual.

Decisões que mais afetam performance na prática:

  • Chave primária: tamanho, ordenação e estabilidade ao longo do tempo.
  • Tipo de dado: data como date, valor como decimal, estado como domínio fechado.
  • Normalização versus duplicação controlada, com custo de sincronização explícito.
  • Cardinalidade das colunas filtradas com frequência.
  • Granularidade: separar entidades que hoje convivem na mesma tabela.

PostgreSQL e MySQL: a mesma modelagem, comportamentos diferentes

A modelagem é conceitualmente parecida, mas os dois motores reagem de forma distinta às mesmas escolhas.

No MySQL com InnoDB, a chave primária é a tabela. Os dados ficam organizados fisicamente por ela, e todo índice secundário carrega a chave primária junto. Uma chave aleatória, como UUID gerado sem ordenação temporal, provoca inserções espalhadas, divisão de páginas e índices secundários maiores. Chaves sequenciais ou UUID ordenável por tempo tendem a se comportar melhor nesse cenário.

No PostgreSQL, a tabela é um heap e a chave primária é apenas mais um índice. Atualizações geram novas versões de linha, o que torna padrões de escrita intensa dependentes de vacuum e do fillfactor configurado. Em compensação, o PostgreSQL oferece recursos que mudam a modelagem: tipos ricos como jsonb, arrays e ranges, índices parciais, índices por expressão e estatísticas mais detalhadas por coluna.

Outras diferenças que aparecem no dia a dia:

  • Índices parciais são nativos no PostgreSQL e exigem colunas geradas no MySQL.
  • jsonb tem operadores e indexação GIN no PostgreSQL; no MySQL, indexar JSON costuma passar por colunas geradas.
  • Tratamento de fuso horário difere entre timestamptz e os tipos datetime e timestamp.
  • Colações e comparação de texto afetam ordenação e uso de índice nos dois motores.

Exemplo concreto: uma distribuidora com tabela de pedidos e itens. O time armazenava a data do pedido como texto e o status como varchar livre, porque era mais simples para importar planilhas. Quando o volume cresceu, os relatórios de faturamento por período passaram a varrer a tabela inteira, e cada ajuste de status exigia atualização em massa. Reorganizar apenas dois campos, data como date e status como domínio controlado, mudou o custo das consultas e reduziu o retrabalho de correção de dados. Nenhum índice novo foi necessário para o relatório principal.

Decidir o modelo antes de escalar

Revisar modelo é mais barato antes do crescimento. Depois que a base tem anos de histórico, mudar tipo de coluna ou chave primária envolve janela de manutenção, migração de dados e risco de regressão em sistemas que dependem daquele esquema. Vale tratar a modelagem como decisão de arquitetura, registrada e revisada junto com as integrações, e não como detalhe de implementação.

Na prática

Para quem mantém software em produção com PostgreSQL ou MySQL:

  • Revise tipos das colunas usadas em filtros e joins antes de criar qualquer índice.
  • Documente a escolha de chave primária e o motivo, especialmente quando houver UUID.
  • Trate denormalização como decisão registrada, com rotina de sincronização definida.
  • Compare planos de execução antes e depois de mudanças de esquema.
  • No MySQL, avalie o impacto da chave primária sobre os índices secundários.
  • No PostgreSQL, acompanhe bloat e vacuum em tabelas com muita atualização.
  • Inclua revisão de modelo no processo de mudança, não só revisão de código.