O PostgreSQL 18 chegou em setembro de 2025 e não é exagero dizer que essa é a release mais significativa dos últimos anos. Depois de duas décadas usando o mesmo protocolo de comunicação, de anos dependendo de I/O síncrono e de gambiarras para gerar UUIDs ordenáveis, essa versão finalmente resolve dores reais que qualquer desenvolvedor backend já sentiu.
Neste post, vamos passar pelas 7 novidades mais impactantes do PostgreSQL 18 com exemplos práticos de SQL que você pode rodar hoje mesmo.
O que o PostgreSQL 18 traz de novo
Antes de mergulhar nos detalhes, vale entender o panorama geral. O PostgreSQL 18 foca em três frentes: performance (I/O assíncrono e skip scan), produtividade do desenvolvedor (UUIDv7, colunas virtuais, RETURNING melhorado) e integridade de dados (constraints temporais).
Além disso, o protocolo de comunicação client-server foi atualizado pela primeira vez desde 2003 — da versão 3.0 para 3.2. Isso abre caminho para otimizações futuras na comunicação entre aplicação e banco.
O tema comum entre todas essas mudanças? O PostgreSQL está eliminando a necessidade de soluções externas para problemas que deveriam ser resolvidos no próprio banco.
I/O assíncrono: o fim do gargalo de disco
Historicamente, o PostgreSQL executava operações de I/O de forma síncrona. Cada leitura de disco bloqueava o processo até receber os dados de volta. Em workloads com muita leitura sequencial, isso criava um gargalo real — o banco ficava esperando o disco quando poderia estar processando outras requisições.
O PostgreSQL 18 introduz um subsistema de I/O assíncrono (AIO) que permite disparar múltiplas requisições de leitura em paralelo, sem esperar cada uma terminar antes de iniciar a próxima.
Para verificar o modo de I/O ativo na sua instância:
SHOW io_method;
Os valores possíveis são sync (legado), worker (processos dedicados para I/O) e io_uring (interface nativa do Linux para I/O assíncrono, a mais rápida).
Você também pode verificar quantos workers estão ativos:
SHOW io_workers;
Os ganhos reportados pela comunidade são expressivos: 2 a 3x de melhoria em sequential scans para workloads de leitura intensiva. O impacto é ainda mais perceptível em ambientes cloud, onde a latência de disco é naturalmente mais alta que em SSDs locais.
Um ponto importante: o AIO beneficia principalmente scans sequenciais e bitmap heap scans. Index scans pontuais, que já são rápidos por natureza, não apresentam ganho significativo.
Index skip scan: queries rápidas sem índices extras
Quantas vezes você criou um índice extra só porque sua query não usava a primeira coluna de um índice composto? O skip scan resolve exatamente esse problema.
Antes do PostgreSQL 18, se você tinha um índice em (region, category, date) e fazia uma query filtrando apenas por category e date, o banco ignorava o índice completamente e fazia um sequential scan. A solução era criar outro índice em (category, date).
Agora, o B-tree skip scan permite que o PostgreSQL "pule" entre os valores da primeira coluna e leia apenas as porções relevantes do índice:
-- Índice existente: (region, category, date)
-- Antes do PG 18: sequential scan (ignorava o índice)
-- PG 18: skip scan (usa o índice pulando entre regions)
SELECT *
FROM sales
WHERE category = 'Electronics'
AND date > '2025-01-01';
O skip scan funciona melhor quando a coluna de prefixo tem baixa cardinalidade — ou seja, poucos valores distintos. Se a coluna region tem 5 valores possíveis, o banco faz 5 buscas pontuais no índice. Se tem 5 milhões, a vantagem desaparece.
Para verificar se o otimizador está usando skip scan, basta analisar o plano de execução:
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM sales
WHERE category = 'Electronics'
AND date > '2025-01-01';
O node no resultado continua sendo Index Scan ou Index Only Scan — não existe um tipo separado "Skip Scan". O indicador é o campo Index Searches: N que aparece no output do EXPLAIN ANALYZE. Se N for maior que 1, significa que o PostgreSQL fez múltiplas buscas no índice, pulando entre valores distintos da coluna de prefixo — ou seja, skip scan ativo.
UUIDv7 nativo: identificadores com ordem temporal
O debate entre auto-increment e UUID como chave primária é antigo. Auto-increment é eficiente para o B-tree mas vaza informação sobre o volume de dados. UUID v4 é aleatório e seguro, mas fragmenta o índice horrivelmente porque os valores não têm nenhuma ordem.
O UUIDv7 resolve esse impasse. Ele embute um timestamp de milissegundos nos primeiros 48 bits, o que significa que UUIDs gerados em sequência são automaticamente ordenáveis por tempo. O B-tree adora isso.
No PostgreSQL 18, a geração é nativa:
SELECT uuidv7();
-- 01980de8-ad3d-715c-b739-faf2bb1a7aad
Você também pode extrair o timestamp de um UUIDv7 existente:
SELECT uuid_extract_timestamp(
'01980de8-ad3d-715c-b739-faf2bb1a7aad'
);
-- 2025-09-24 12:30:15.123+00
E até gerar UUIDs com offset temporal, útil para testes e dados históricos:
SELECT uuidv7(INTERVAL '-7 days');
-- Gera um UUIDv7 com timestamp de 7 dias atrás
Na prática, se você está começando um projeto novo com PostgreSQL 18, use uuidv7() como default da chave primária:
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuidv7(),
customer_id UUID NOT NULL,
total DECIMAL(10,2) NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
Os benefícios são imediatos: menos fragmentação de índice comparado ao UUID v4, ordenação natural por tempo de criação e zero dependência de bibliotecas externas.
Colunas virtuais geradas: dados calculados sem armazenamento
Colunas geradas existem desde o PostgreSQL 12, mas até a versão 17 só existia o tipo STORED — o valor era calculado e gravado fisicamente no disco a cada INSERT ou UPDATE. Isso consumia espaço e adicionava overhead de escrita.
O PostgreSQL 18 introduz colunas VIRTUAL (e as torna o padrão). Elas são calculadas no momento da leitura, sem ocupar espaço em disco:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price DECIMAL(10,2) NOT NULL,
tax_rate DECIMAL(5,4) DEFAULT 0.0875,
total DECIMAL(10,2) GENERATED ALWAYS AS (price * (1 + tax_rate))
);
Como VIRTUAL agora é o default, não precisa especificar. Mas se quiser ser explícito:
-- Explicitamente virtual
full_name TEXT GENERATED ALWAYS AS (first_name || ' ' || last_name) VIRTUAL
-- Explicitamente armazenada (comportamento antigo)
full_name TEXT GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED
Quando usar cada tipo? VIRTUAL é ideal quando o cálculo é barato e a coluna é lida com pouca frequência — evita desperdício de espaço. STORED compensa quando o cálculo é caro ou a coluna é lida constantemente, pois o custo é pago na escrita.
Um detalhe importante: colunas VIRTUAL não podem ser indexadas diretamente. Se você precisa de um índice na coluna gerada, use STORED.
RETURNING com OLD e NEW: DML mais expressivo
A cláusula RETURNING já existia no PostgreSQL, mas retornava apenas os valores finais da operação. Se você precisava saber o valor anterior de uma coluna antes do UPDATE, tinha que fazer um SELECT antes ou usar um trigger.
O PostgreSQL 18 adiciona os qualificadores OLD e NEW, inspirados na sintaxe de triggers:
UPDATE users
SET email = 'novo@exemplo.com'
WHERE id = 42
RETURNING OLD.email AS email_anterior,
NEW.email AS email_atual;
Resultado:
email_anterior | email_atual
---------------------+--------------------
antigo@exemplo.com | novo@exemplo.com
Isso é especialmente útil para audit logs sem precisar de triggers:
WITH changes AS (
UPDATE products
SET price = price * 1.10
WHERE category = 'premium'
RETURNING id,
OLD.price AS preco_antigo,
NEW.price AS preco_novo
)
INSERT INTO price_history (product_id, old_price, new_price, changed_at)
SELECT id, preco_antigo, preco_novo, now()
FROM changes;
O OLD e NEW também funcionam com DELETE (onde NEW é sempre NULL) e com INSERT (onde OLD é sempre NULL). Em comandos MERGE, ambos estão disponíveis dependendo da ação executada.
Constraints temporais e o novo protocolo 3.2
Duas novidades que merecem destaque juntas por serem mais especializadas, mas igualmente impactantes.
WITHOUT OVERLAPS: constraints sobre períodos
Se você já implementou um sistema de reservas, sabe a dor de validar que dois períodos não se sobrepõem. Antes, isso exigia triggers ou lógica na aplicação. O PostgreSQL 18 traz a cláusula WITHOUT OVERLAPS para constraints de chave primária e unique:
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE room_bookings (
room_id INT NOT NULL,
during TSTZRANGE NOT NULL,
guest_name TEXT NOT NULL,
PRIMARY KEY (room_id, during WITHOUT OVERLAPS)
);
Agora, tentar inserir uma reserva que conflita com outra gera um erro de constraint:
-- Reserva existente: sala 101, 1 a 5 de março
INSERT INTO room_bookings VALUES
(101, '[2026-03-01, 2026-03-05)', 'Alice');
-- Tentativa de reserva conflitante: erro!
INSERT INTO room_bookings VALUES
(101, '[2026-03-03, 2026-03-07)', 'Bob');
-- ERROR: conflicting key value violates exclusion constraint
O banco garante a integridade no nível de constraint, sem código extra na aplicação.
Protocolo 3.2: a primeira atualização em 22 anos
O protocolo wire do PostgreSQL não era atualizado desde a versão 7.4, lançada em 2003. O PostgreSQL 18 introduz a versão 3.2, mantendo compatibilidade com clientes existentes (que continuam negociando 3.0).
A versão 3.2 em si não traz funcionalidades visíveis para o desenvolvedor, mas abre a porta para melhorias futuras como negociação de features entre client e server, otimizações no formato de mensagens e suporte a novos mecanismos de autenticação — como o OAuth, que também estreia no PostgreSQL 18.
Conclusão
O PostgreSQL 18 não é uma release incremental. Cada novidade resolve um problema concreto que desenvolvedores enfrentam diariamente: I/O assíncrono elimina o gargalo de disco em leituras pesadas, skip scan reduz a necessidade de índices duplicados, UUIDv7 entrega identificadores únicos e ordenáveis sem extensões, colunas virtuais evitam armazenamento desnecessário e o RETURNING com OLD/NEW simplifica audit logs.
Se você ainda está no PostgreSQL 16 ou 17, a migração vale o esforço. Comece testando em um branch de desenvolvimento — se você usa Neon, basta criar um branch do banco de produção, rodar seus testes e validar a compatibilidade. Os ganhos de performance com o AIO sozinho já justificam o upgrade.
A cada release, o PostgreSQL confirma porque segue sendo a escolha padrão para quem leva banco de dados a sério.