SQL

PostgreSQL JSONB: consulte dados aninhados e escolha o índice GIN certo

PG Monitoring Team July 11, 2026 9 min de leitura

JSONB é útil para atributos que realmente variam entre linhas: payloads de webhook, metadados de produto, configurações de integração ou propriedades de eventos. Não é uma desculpa para esconder um modelo relacional em uma coluna. A diferença aparece quando a tabela cresce e cada predicado JSON vira um sequential scan.

Dados de exemplo

CREATE TABLE events (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  created_at timestamptz NOT NULL DEFAULT now(),
  payload jsonb NOT NULL
);

INSERT INTO events (payload) VALUES
('{"type":"payment","amount":89.90,"customer":{"country":"BR"},"tags":["vip","mobile"]}'),
('{"type":"signup","customer":{"country":"US"},"tags":["web"]}');

Leia valores simples e aninhados

-- Valor JSON
SELECT payload -> 'customer' AS customer
FROM events;

-- Valor texto, adequado para comparação
SELECT id, payload ->> 'type' AS event_type
FROM events
WHERE payload ->> 'type' = 'payment';

-- Valor texto aninhado
SELECT id
FROM events
WHERE payload #>> '{customer,country}' = 'BR';

Use -> quando a próxima operação precisa de JSONB e ->> quando você precisa de texto. Misturar os dois é causa frequente de erros confusos de operador.

Containment: o melhor encaixe para um índice GIN geral

O operador de contenção @> pergunta se um documento contém um fragmento JSON. Ele combina naturalmente com um índice GIN de propósito geral.

CREATE INDEX events_payload_gin_idx
ON events USING gin (payload);

SELECT id, created_at
FROM events
WHERE payload @> '{"type":"payment","customer":{"country":"BR"}}';

Use EXPLAIN (ANALYZE, BUFFERS) depois de criar o índice. O PostgreSQL pode corretamente escolher sequential scan numa tabela pequena ou para um predicado pouco seletivo.

Índices de expressão para uma chave muito consultada

Quando uma chave JSON é filtrada o tempo todo, um expression index B-tree pequeno geralmente é melhor que um GIN geral grande.

CREATE INDEX events_type_idx
ON events ((payload ->> 'type'));

SELECT COUNT(*)
FROM events
WHERE payload ->> 'type' = 'payment';

Para uma chave aninhada, indexe exatamente a expressão usada pela query:

CREATE INDEX events_country_idx
ON events ((payload #>> '{customer,country}'));

SELECT id
FROM events
WHERE payload #>> '{customer,country}' = 'BR';

Arrays e existência

-- O documento possui uma chave no nível superior?
SELECT id FROM events WHERE payload ? 'amount';

-- tags contém "vip"?
SELECT id FROM events WHERE payload @> '{"tags":["vip"]}';

Um índice GIN padrão suporta esses operadores. Não suponha que ele acelera toda expressão JSONPath; valide o plano para o operador exato que você usa.

Quando JSONB é o modelo errado

  • Um valor é obrigatório em toda linha e participa de joins.
  • Você precisa de foreign keys, unicidade ou restrições de faixa nele.
  • Você o ordena ou agrega na maior parte das requisições.
  • A chave é central ao domínio do produto, não metadado incidental.

Nesses casos, modele o valor como coluna tipada. Mantenha atributos flexíveis e esparsos em JSONB e mova campos estáveis e muito acessados para colunas conforme a aplicação amadurece.

Custo de escrita: índices GIN aceleram leituras, mas adicionam trabalho a cada insert e update. Indexe somente caminhos JSONB que sua carga realmente consulta e monitore tamanho de índice e latência de escrita após o rollout.

O PG Monitoring ajuda mostrando quais predicados JSONB consomem tempo, se um índice mudou o plano e se a manutenção de índice está virando parte do gargalo de escrita.

Related Articles

Ready to experience better PostgreSQL monitoring?

Join thousands of teams who switched from traditional tools to PG Monitoring's AI-powered platform.

Fale conosco