Tecnologias/05 - Comparações7 min
PostgreSQL vs MySQL vs Oracle
Conceito
Os três falam SQL, implementam ACID, têm índice B-tree, MVCC, replicação e transação. Se a comparação parar aí, os três parecem intercambiáveis — e é exatamente por isso que projetos de migração estouram o prazo.
A diferença real está em decisões internas de arquitetura que vazam para o seu código e para a sua operação. São seis, e vale conhecer cada uma pelo nome.
Panorama
| PostgreSQL | MySQL (InnoDB) | Oracle | |
|---|---|---|---|
| Tese | correção e extensibilidade | simplicidade de operação | completude e garantia |
| Conexão | processo (~5–10 MB) | thread | processo/thread dedicado |
| Layout da tabela | heap + índices | índice clusterizado pela PK | heap (ou IOT, opcional) |
| Versões antigas (MVCC) | na própria tabela | undo log | undo tablespace |
| Limpeza | VACUUM / autovacuum |
reciclagem do undo | reciclagem do undo |
| Isolamento padrão | READ COMMITTED |
REPEATABLE READ |
READ COMMITTED |
READ UNCOMMITTED |
não implementa (aceita a sintaxe) | implementa | não existe |
| DDL transacional | sim | não (commit implícito) | não (commit implícito) |
| Índice parcial / por expressão | sim / sim | não / functional index (8.0) | não / function-based index |
| Tipos além do básico | array, jsonb, range, hstore, geo |
JSON, VECTOR (9.0+) |
XMLType, JSON, VECTOR (26ai) |
RETURNING |
sim | não | via RETURNING INTO (PL/SQL) |
| Extensões de terceiros | PostGIS, pgvector, Timescale… | limitado | opções pagas da própria Oracle |
| Particionamento | nativo, incluso | nativo, incluso | opção paga na EE |
| Failover automático | externo (Patroni, RDS) | InnoDB Cluster / Group Replication | Data Guard / RAC |
| Cluster ativo-ativo | não | Group Replication (multi-primário, com ressalvas) | RAC (disco compartilhado) |
| Viagem no tempo | PITR pelo WAL | PITR pelo binlog | Flashback (query, tabela, banco) |
| Licença | PostgreSQL (permissiva) | GPL + comercial (Oracle) | proprietária, por core + opções |
| Ciclo de versões | 1 major/ano, 5 anos de suporte | LTS ~2 anos + Innovation trimestral | LTS de longa duração (19c, 26ai) |
| Versão atual (set/2026) | 18 (18.6) | 9.7 LTS / 26.7 Innovation | 26ai (19c até 2032) |
As 6 diferenças que realmente mudam seu trabalho
1. Onde ficam as versões antigas → define como o banco é operado
No Postgres, um UPDATE grava uma nova versão da linha na própria tabela e marca a
antiga como morta. No MySQL e no Oracle, a linha é alterada no lugar e a versão anterior
vai para o undo.
| Postgres | MySQL / Oracle | |
|---|---|---|
Custo do UPDATE |
escreve linha nova + atualiza todos os índices | altera no lugar; índice só se a coluna indexada mudou |
| Problema crônico | bloat: tabela e índice incham | undo crescendo; ORA-01555 / history list |
| Rollback | quase instantâneo (descarta a versão nova) | precisa desfazer pelo undo (pode demorar) |
| Ferramenta de convivência | monitorar autovacuum, pg_repack |
monitorar undo, encurtar transações longas |
Uma transação
idle in transactionesquecida no Postgres impede oVACUUMde limpar qualquer coisa mais nova que ela. No Oracle, ela causasnapshot too old. Mesmo pecado, punição diferente.
2. Heap vs índice clusterizado → define a chave primária
No InnoDB a tabela é a árvore da PK (1. MySQL). No Postgres e no Oracle a tabela é um heap e a PK é só mais um índice.
PK aleatória (UUID v4):
MySQL → insere no meio da árvore: page splits, I/O aleatório, índices secundários gordos
Postgres → insere no fim do heap; só o índice sofre um pouco
Prático: UUID v4 como PK é um problema de performance no MySQL e quase um não-problema
no Postgres. No MySQL, use AUTO_INCREMENT ou UUID v7 em BINARY(16). Nos dois
últimos, uuidv7() (Postgres 18) ainda é uma boa ideia por localidade de índice, mas não
é emergência.
3. DDL transacional → define como você faz migração
BEGIN;
ALTER TABLE a ADD COLUMN x int;
ALTER TABLE b ADD COLUMN y int;
ROLLBACK;
- Postgres: desfaz as duas. Migração é atômica; se falhar no meio, não aconteceu.
- MySQL / Oracle: cada
ALTERfaz commit implícito. Falhar na segunda deixa a primeira aplicada.
Isso muda a estratégia, não só a ferramenta: em MySQL e Oracle toda migração precisa ser escrita para ser idempotente e retomável, porque um estado intermediário é possível. É a origem do ecossistema gh-ost / pt-online-schema-change no MySQL.
4. Isolamento padrão → o mesmo código dá resultados diferentes
O padrão do MySQL é REPEATABLE READ; dos outros dois, READ COMMITTED.
-- Sessão A -- Sessão B
BEGIN;
SELECT saldo FROM contas WHERE id=1; -- 100
UPDATE contas SET saldo=200 WHERE id=1; COMMIT;
SELECT saldo FROM contas WHERE id=1;
-- MySQL: 100 (snapshot do início da transação)
-- Postgres: 200 (vê o commit de B)
-- Oracle: 200
Nenhum está errado — mas um relatório que soma em duas queries dentro da mesma transação
é consistente no MySQL e pode ser inconsistente no Postgres. E vice-versa: lógica
que depende de "ler o valor mais recente" para decidir quebra no MySQL. Além disso o
REPEATABLE READ do MySQL traz gap locks, que produzem deadlocks que simplesmente
não existem nos outros. Ver 3. MVCC e Isolamento na Prática.
5. Custo da conexão → define a arquitetura do app
| Custo por conexão | Teto prático | Consequência | |
|---|---|---|---|
| Postgres | processo, ~5–10 MB | centenas | pooler é obrigatório (PgBouncer) |
| MySQL | thread | milhares | pool no app costuma bastar |
| Oracle | processo dedicado ou shared server | depende da configuração | connection pooling (DRCP/UCP) é padrão |
Junte isso com goroutines baratas (2. Goroutines e Channels) e o padrão
MaxOpenConns ilimitado do database/sql: no Postgres, um pico de tráfego derruba o
banco; no MySQL, provavelmente só fica lento. Ver 5. Go em Produção.
6. Dialeto e semântica → define quanto seu SQL é portável
- Oracle:
''éNULL; concatenação ignoraNULL;FROM dual;VARCHAR2;NUMBERpara tudo; semBOOLEANem SQL antes do 23ai. - MySQL:
||significa OR (não concatenação, salvoPIPES_AS_CONCAT);BOOLEANéTINYINT(1); identificadores em`crase`; sensibilidade a maiúsculas em nome de tabela depende do sistema de arquivos. - Postgres: identificadores em
"aspas duplas"; semRETURNINGvocê não vive depois de ter usado.
O catálogo completo está em 2. Dialetos SQL na Prática.
Trade-offs
Portabilidade custa features. Escrever SQL que roda nos três significa abrir mão de
jsonb, índice parcial, RETURNING, SKIP LOCKED com dialeto próprio, tipos
específicos — ou seja, abrir mão exatamente do que faz um banco valer a escolha. ORM
(GORM, sqlc, Hibernate) esconde parte disso e vaza justamente onde dói: tipos, lock,
paginação, migração.
A escolha "certa" na maioria dos casos é chata. Em 2026, para um serviço novo sem requisito institucional, PostgreSQL é a escolha padrão defensável: mais recursos, licença permissiva, extensões e gerenciados em toda nuvem. MySQL vence quando a operação é o gargalo, quando a carga é dominada por leitura por PK, ou quando o time já opera MySQL há dez anos. Oracle não é uma escolha nova — é uma herança a administrar, e o trabalho normalmente é migrar dele.
O trade-off central: o modelo relacional é o mesmo nos três; o que você compra é o modo de operar. Postgres pede vigilância e devolve recursos. MySQL pede pouco e devolve pouco. Oracle pede dinheiro e devolve garantia.
Exemplo prático
Como escolher, em ordem de peso das perguntas:
Existe requisito institucional (contrato, certificação, ERP, PL/SQL legado)?
└─ sim → Oracle. A discussão é de migração, não de escolha.
Preciso de tipos ricos, jsonb, geo, vetor, índice parcial, DDL transacional?
└─ sim → PostgreSQL.
Minha carga é 95% leitura por chave primária, com time que já opera MySQL?
└─ sim → MySQL. É onde o índice clusterizado brilha.
Preciso escalar escrita além de um nó?
└─ nenhum dos três resolve sozinho: [[Sharding]], [[Federation]] ou outro modelo
([[1. MongoDB]], [[DynamoDB]]).
Nenhuma das anteriores?
└─ PostgreSQL. Escolha padrão por eliminação — e a menos arrependível.
O mesmo UPSERT nos três
-- PostgreSQL
INSERT INTO estoque (sku, qtd) VALUES ('A1', 5)
ON CONFLICT (sku) DO UPDATE SET qtd = estoque.qtd + EXCLUDED.qtd
RETURNING qtd; -- devolve o resultado de graça
-- MySQL
INSERT INTO estoque (sku, qtd) VALUES ('A1', 5)
ON DUPLICATE KEY UPDATE qtd = qtd + VALUES(qtd);
-- sem RETURNING: precisa de um SELECT depois (e de cuidado com concorrência)
-- Oracle
MERGE INTO estoque e
USING (SELECT 'A1' sku, 5 qtd FROM dual) s ON (e.sku = s.sku)
WHEN MATCHED THEN UPDATE SET e.qtd = e.qtd + s.qtd
WHEN NOT MATCHED THEN INSERT (sku, qtd) VALUES (s.sku, s.qtd);
Três sintaxes, três comportamentos de lock diferentes sob concorrência, e um RETURNING
que existe em um só. Este bloco resume a nota inteira: a operação é a mesma, o
contrato não é.
Aprofundamento aplicado
Comparar a mesma regra sob as mesmas exigências
Use uma reserva de estoque como unidade de comparação: mesma distribuição de dados, mesma concorrência, saldo inicial e comportamento esperado após falhas. A consulta que vence um benchmark isolado pode perder quando inclui locks, índices e durabilidade.
| Dimensão | Evidência útil |
|---|---|
| Correção | A reserva nunca confirma além do estoque |
| Desempenho | Throughput e percentis de latência sob concorrência |
| Recuperação | Resultado de queda durante transação e confirmação |
| Operação | Trabalho de índices, versões antigas, backups e migrações |
| Compatibilidade | Comportamento dos comandos realmente usados |
Defaults são parte da diferença
O mesmo código pode encontrar snapshots e locks diferentes conforme banco, engine e isolamento da sessão. Declare essas opções no experimento. Nome parecido para um nível de isolamento não demonstra implementação ou anomalias idênticas.
Exercício: execute a regra em duas sessões, com ambos tentando reservar o último item. Resultado esperado: documente espera, erro ou retry e como o aplicativo chega a exatamente uma confirmação. A escolha final deve incluir a operação que o time consegue manter, além da medição.
Referências: PostgreSQL, InnoDB e Oracle.
Relacionado
- 1. PostgreSQL
- 1. MySQL
- 1. Oracle Database
- 2. Dialetos SQL na Prática
- 3. MVCC e Isolamento na Prática
- SQL vs NoSQL
- 4. MongoDB vs Relacional
- RDS
Parte de MOC Comparações · postgresql.org/docs · dev.mysql.com/doc · docs.oracle.com/database