← Voltar ao blog

PostgreSQL vs MySQL: como escolher o banco certo

JSONB, DDL transacional, upsert e índice GIN: compare PostgreSQL e MySQL nos pontos que mudam decisões reais e veja quando cada um é a escolha errada.


A escolha entre PostgreSQL e MySQL raramente é decidida por velocidade bruta: os dois atendem bem a maioria dos sistemas. Ela é decidida por tipos de dados, comportamento transacional, recursos de consulta e o que a equipe já sabe operar. Este guia compara os pontos que mudam decisões reais e lista quando cada um é a escolha errada.

Os dois são open source, rodam em qualquer nuvem e têm ferramental maduro. A diferença aparece nos detalhes que só doem depois: DDL transacional, JSON de verdade, licença e comportamento de agrupamento.


Quem é melhor para dados semi-estruturados?

O PostgreSQL, com folga. Ele tem JSONB binário e indexável, arrays nativos, tipos compostos e um catálogo de extensões que o MySQL não tem equivalente próximo.

-- PostgreSQL: JSONB com índice GIN
CREATE TABLE EVENTOS (
  ID       BIGSERIAL PRIMARY KEY,
  PAYLOAD  JSONB NOT NULL,
  CRIADO   TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX IDX_EVENTOS_PAYLOAD ON EVENTOS USING GIN (PAYLOAD);
-- MySQL: JSON funcional, mas sem índice GIN; a saída é coluna gerada + índice
CREATE TABLE EVENTOS (
  ID      BIGINT AUTO_INCREMENT PRIMARY KEY,
  PAYLOAD JSON NOT NULL,
  TIPO    VARCHAR(40) GENERATED ALWAYS AS (JSON_UNQUOTE(PAYLOAD->'$.tipo')) STORED,
  KEY IDX_EVENTOS_TIPO (TIPO)
);

Se o sistema guarda configuração, log de integração ou payload de webhook, o PostgreSQL resolve sem gambiarra. No MySQL dá para fazer, mas cada consulta nova vira uma decisão sobre coluna gerada e índice.

Como os dois se comportam com transação e DDL?

O PostgreSQL tem DDL transacional: CREATE TABLE, ALTER TABLE e DROP entram na transação e podem ser desfeitos com ROLLBACK. O MySQL faz commit implícito antes e depois de comandos DDL — não há como desfazer um ALTER no meio do caminho.

-- PostgreSQL: dá para desfazer
BEGIN;
ALTER TABLE CLIENTES ADD COLUMN TELEFONE VARCHAR(20);
-- algo deu errado?
ROLLBACK;
-- MySQL: o ALTER já foi confirmado; ROLLBACK não desfaz
ALTER TABLE CLIENTES ADD COLUMN TELEFONE VARCHAR(20);
ROLLBACK; -- não desfaz o ALTER

Em migração de esquema com script versionado, essa diferença pesa: no MySQL, todo ALTER precisa ser escrito para falhar de forma segura, porque não existe rede de proteção.

O mesmo problema nos dois bancos

Tarefa PostgreSQL MySQL
Auto incremento GENERATED BY DEFAULT AS IDENTITY ou SERIAL AUTO_INCREMENT
Upsert ON CONFLICT (chave) DO UPDATE ON DUPLICATE KEY UPDATE
Concatenar texto a || b ou concat(a, b) concat(a, b)
Data e hora com fuso TIMESTAMPTZ TIMESTAMP (converte pelo fuso da sessão)
Paginação LIMIT n OFFSET m LIMIT m, n
Diferença de conjuntos EXCEPT EXCEPT (8.0.31+)
Booleano BOOLEAN nativo TINYINT(1)
Bloqueio de linha FOR UPDATE FOR UPDATE

Upsert é o caso que mais aparece em integração:

-- PostgreSQL
INSERT INTO CLIENTES (CPF, NOME) VALUES ('12345678901', 'Ana')
ON CONFLICT (CPF) DO UPDATE SET NOME = EXCLUDED.NOME;
-- MySQL
INSERT INTO CLIENTES (CPF, NOME) VALUES ('12345678901', 'Ana')
ON DUPLICATE KEY UPDATE NOME = VALUES(NOME);

Note que CPF precisa ser chave primária ou índice único nos dois — sem isso, o upsert vira insert duplicado.

Onde o MySQL leva vantagem?

Três cenários concretos:

  • Replicação simples e read-heavy: réplica de leitura é trivial de montar e a operação é conhecida por qualquer time.
  • Compatibilidade de ecossistema: hospedagem compartilhada, painéis e CMSs populares assumem MySQL.
  • Equipe já treinada: trocar de banco custa mais do que a diferença técnica entre os dois.

E um alerta: o modo ONLY_FULL_GROUP_BY precisa estar ligado. Desligado, ele aceita SELECT com coluna fora do GROUP BY e devolve valor arbitrário — comportamento que gera total errado sem erro nenhum.

Outro ponto operacional: no MySQL, o innodb_buffer_pool_size é a configuração que mais muda desempenho e vem com valor conservador por padrão. Servidor com 32 GB rodando com pool de 128 MB vai parecer lento por motivo nenhum, e o ajuste é uma linha no my.cnf.

-- MySQL com ONLY_FULL_GROUP_BY desligado: aceita e devolve valor arbitrário
SELECT IDCLIENTE, DATA, SUM(VALOR)
FROM LANCAMENTOS
GROUP BY IDCLIENTE;

Onde o PostgreSQL leva vantagem?

  • Consultas analíticas: CTE recursiva, window functions, GROUPING SETS, FILTER, DISTINCT ON e LATERAL no mesmo lugar.
  • Índices: GIN, GiST, BRIN e índice parcial, todos usáveis de forma direta.
  • Consistência de tipos: NUMERIC para dinheiro, TIMESTAMPTZ para tempo, UUID nativo.
  • Extensões: PostGIS, pg_trgm para busca aproximada, pgcrypto.

DISTINCT ON resolve "último lançamento por cliente" sem subconsulta:

SELECT DISTINCT ON (IDCLIENTE) IDCLIENTE, DATA, VALOR
FROM LANCAMENTOS
ORDER BY IDCLIENTE, DATA DESC;

Migrar de um para o outro vale a pena?

Só com motivo específico: recurso que o banco atual não tem, custo de licença, ou limite de operação. "PostgreSQL é melhor" não é motivo — é opinião.

Antes de decidir, meça:

  1. Volume e padrão de acesso: escrita intensa, leitura intensa ou misto?
  2. Recursos usados hoje: JSON, arrays, procedures, extensões.
  3. Consulta pesada: qual é a query mais cara do sistema e como ela se comporta nos dois?
  4. Operação: backup, réplica, upgrade, monitoramento.
  5. Equipe: quem resolve problema às 3h da manhã?

Checklist de decisão

  • O sistema depende de JSON consultável e indexável?
  • Existe script de migração de esquema que precisa ser reversível?
  • A equipe opera o banco escolhido com segurança?
  • A hospedagem ou o provedor suporta a escolha?
  • Replicação e backup estão cobertos?
  • A query mais cara foi testada nos dois?
  • ONLY_FULL_GROUP_BY está ligado (se MySQL)?
  • O charset é utf8mb4, não utf8 (se MySQL)?

Perguntas frequentes

PostgreSQL é sempre mais rápido que MySQL?

Não. Para leitura simples com índice bem ajustado, os dois empatam. A diferença aparece em consultas analíticas, tipos complexos e comportamento transacional, não em SELECT por chave primária.

Posso usar MySQL com JSON e ficar tranquilo?

Sim, desde o 5.7 o MySQL tem tipo JSON e funções de extração. O limite é indexação: você indexa colunas geradas, não o documento inteiro, o que exige planejar cada consulta.

Migrar de MySQL para PostgreSQL quebra a aplicação?

Quebra em pontos previsíveis: AUTO_INCREMENT, ON DUPLICATE KEY, LIMIT m, n, TINYINT(1) como booleano e funções específicas de string e data. Um inventário de sintaxe antes da migração evita a maior parte das surpresas.

Conclusão

PostgreSQL vence em recursos de dados e consistência transacional; MySQL vence em simplicidade operacional e ecossistema. Para sistema novo com dados complexos, PostgreSQL é a escolha de menor atrito. Para aplicação simples, equipe acostumada e hospedagem padrão, MySQL resolve sem drama.

Independentemente do banco, a consulta continua sendo o gargalo mais comum. O Sqlify gera SQL a partir da descrição em português e mostra a estrutura de cada cláusula, útil quando você precisa comparar o mesmo problema em dialetos diferentes.

Leia também: