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 ONeLATERALno mesmo lugar. - Índices: GIN, GiST, BRIN e índice parcial, todos usáveis de forma direta.
- Consistência de tipos:
NUMERICpara dinheiro,TIMESTAMPTZpara tempo,UUIDnativo. - 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:
- Volume e padrão de acesso: escrita intensa, leitura intensa ou misto?
- Recursos usados hoje: JSON, arrays, procedures, extensões.
- Consulta pesada: qual é a query mais cara do sistema e como ela se comporta nos dois?
- Operação: backup, réplica, upgrade, monitoramento.
- 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_BYestá ligado (se MySQL)? - O charset é
utf8mb4, nãoutf8(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: