Tipos de Dados PostgreSQL em Profundidade
O sistema de tipos do PostgreSQL não serve apenas para validar a entrada, ele também determina como um valor é armazenado em disco, quais operadores e tipos de índice podem acelerar consultas contra ele, e quanta conversão implícita ocorre antes que uma comparação seja executada.
Esta página é o mapa conceitual para o restante da seção data-types-jsonb, cobrindo como o PostgreSQL categoriza os tipos, como o armazenamento e a indexação seguem essa escolha, e quando usar jsonb, arrays, enums ou domínios em vez de uma coluna estritamente normalizada.
Resumo
- O tipo de uma coluna no PostgreSQL determina seu formato de armazenamento, seus operadores válidos e quais métodos de acesso de índice podem acelerá-la, não apenas quais valores ela aceita.
- Por que Importa: Escolher a categoria de tipo errada pode desativar silenciosamente a indexação, desperdiçar armazenamento ou tornar impossível impor restrições.
- Conceitos Chave: família de tipos, TOAST, classe de operador, métodos de acesso GIN/GiST/B-tree, json vs jsonb.
- Quando Usar: Alcance este modelo ao projetar as colunas de uma nova tabela, ou quando uma consulta que "deveria" usar um índice está fazendo uma varredura sequencial em vez disso.
- Limitações / Trade-offs: Tipos flexíveis como jsonb trocam a força das restrições e a precisão do índice pela flexibilidade do esquema.
- Tópicos Relacionados: tipos numéricos e de texto, arrays e tipos compostos, tipos UUID/enum/domínio, tipos de data e timestamp.
Fundamentos
Cada coluna no PostgreSQL tem exatamente um tipo declarado, e esse tipo restringe quais valores são válidos, como eles se comparam e como eles são ordenados.
O PostgreSQL agrupa seus tipos embutidos em famílias: numéricos, caracteres, booleanos, data/hora, UUID, binários, geométricos, de rede e os tipos semi-estruturados json e jsonb.
Além dos tipos escalares embutidos, o PostgreSQL também suporta tipos compostos construídos a partir de outros tipos, tipos de array que contêm múltiplos de qualquer tipo, tipos de intervalo que representam um lapso de valores, e tipos enum para conjuntos fechados de rótulos.
Um domínio é uma restrição definida pelo usuário sobreposta a um tipo base existente, permitindo que você nomeie e reutilize uma regra de validação como "um endereço de e-mail válido" em várias colunas.
Escolher bigint em vez de int, ou numeric em vez de float, não é uma preferência estilística, muda o comportamento de overflow, a precisão aritmética e a largura do índice.
text e varchar(n) se comportam de forma idêntica no PostgreSQL, exceto pela verificação de comprimento, ao contrário de alguns outros bancos de dados onde varchar carrega uma diferença real de armazenamento ou desempenho.
jsonb armazena JSON em um formato binário decomposto em vez de texto, que é o que o separa do tipo json simples.
Mecânicas e Interações
O PostgreSQL armazena a maioria dos dados da linha em campos de tamanho fixo ou comprimento variável empacotados em páginas de 8 kilobytes, e qualquer valor único maior que cerca de 2 kilobytes é movido para fora da linha para uma tabela lateral chamada TOAST.
TOAST é o motivo pelo qual um grande documento jsonb ou um valor de texto longo não incha cada varredura sequencial da tabela principal, pois o valor superdimensionado só é buscado quando uma consulta realmente o necessita.
Cada tipo pertence a uma ou mais classes de operador, e um índice só pode acelerar operadores que seu método de acesso suporta para esse tipo, que é por isso que nem todo tipo funciona com todo tipo de índice.
Índices B-tree suportam comparações de igualdade e ordenação e funcionam com a maioria dos tipos escalares, enquanto índices GIN suportam operadores de contenção e existência e são o que tornam as consultas jsonb e de array rápidas.
Índices GiST e SP-GiST suportam operadores de estilo geométrico e de sobreposição de intervalo, que é por isso que PostGIS e tipos de intervalo dependem deles em vez de B-tree.
Conversões implícitas e explícitas alteram o tipo de um valor antes da comparação, e uma conversão aplicada a uma coluna indexada em uma cláusula WHERE pode impedir silenciosamente o planejador de usar esse índice.
-- Índice existe em created_at (timestamptz)
CREATE INDEX events_created_at_idx ON app.events (created_at);
-- Sargable: o planejador pode usar o índice
SELECT id FROM app.events WHERE created_at > now() - interval '1 day';
-- Não sargable: a conversão envolve a coluna, o índice fica inutilizável
SELECT id FROM app.events WHERE created_at::date = current_date;A segunda consulta parece equivalente a uma pessoa, mas a conversão em created_at força o PostgreSQL a avaliar uma expressão para cada linha em vez de procurar através do índice.
jsonb também descarta a ordem das chaves, espaços em branco e chaves duplicadas durante sua decomposição, o que o torna mais rápido para consultar, mas significa que ele não pode reproduzir o texto JSON original exato da maneira que json pode.
Tipos Enum armazenam seus valores como pequenos inteiros internamente enquanto exibem o rótulo que você definiu, proporcionando armazenamento compacto com saída legível.
Considerações Avançadas e Aplicações
Decidir entre jsonb e colunas normalizadas é realmente uma decisão sobre a estabilidade do esquema e como os dados serão consultados.
Campos nos quais você filtra, une ou precisa de restrições pertencem a colunas com tipos reais, enquanto atributos genuinamente variáveis ou esparsos são um ajuste razoável para jsonb.
Um índice GIN em uma coluna jsonb acelera consultas de contenção com o operador @>, mas não substitui um índice B-tree para buscas de igualdade em uma única chave bem conhecida, que geralmente requer um índice de expressão em vez disso.
Tipos de intervalo permitem modelar um lapso, como a janela de tempo de uma reserva, como um único valor com operadores de sobreposição embutidos, o que é mais correto e muitas vezes mais rápido do que comparar um par de colunas de início/fim manualmente.
Tipos Enum são rápidos e auto-documentados, mas adicionar ou remover um valor requer uma migração de esquema, o que torna uma tabela de consulta um ajuste melhor quando o conjunto de valores muda com frequência.
Domínios centralizam uma regra de validação em um único local, mas os erros referenciam o nome do domínio em vez da coluna, o que algumas equipes acham menos legível imediatamente no tratamento de erros da aplicação.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Colunas normalizadas | Fortes restrições, melhor suporte a índices e junções | Rígido, precisa de uma migração para cada novo campo | Atributos estáveis e bem conhecidos consultados diretamente |
| Coluna jsonb | Esquema flexível, agrupa atributos esparsos ou variáveis | Restrições mais fracas, índice GIN cobre apenas consultas do tipo contenção | Sacolas de atributos semi-estruturados ou raramente consultados |
| Tipo Enum | Armazenamento compacto, valores auto-documentados | Adicionar um valor requer uma migração | Conjuntos fechados pequenos e raramente alterados |
| Tabela de consulta (chave estrangeira) | Valores alteráveis sem migração, suporta metadados por valor | Uma junção extra para cada leitura | Conjuntos que mudam com frequência ou precisam de atributos extras por valor |
Nenhuma dessas abordagens é mutuamente exclusiva dentro de um esquema, e a maioria dos projetos reais mistura colunas normalizadas, um catch-all jsonb e alguns conjuntos de enum ou tabelas de consulta, dependendo do campo.
Equívocos Comuns
- jsonb é sem esquema, então não tem custo - jsonb ainda acarreta custos de armazenamento e indexação, e pular colunas com tipos para tudo torna as restrições e junções mais difíceis de impor.
- varchar(n) é mais rápido que text - no PostgreSQL eles usam o mesmo armazenamento subjacente e
varchar(n)apenas adiciona uma verificação de comprimento na escrita. - Ponto flutuante é bom para dinheiro, desde que você arredonde cuidadosamente - ponto flutuante não pode representar a maioria das frações decimais exatamente, então
numericé o tipo correto para moeda, independentemente da disciplina de arredondamento. - Qualquer índice pode acelerar qualquer consulta em uma coluna - um índice só acelera os operadores que seu método de acesso realmente suporta para esse tipo, que é por isso que a contenção jsonb precisa de GIN, não B-tree.
- json e jsonb são basicamente o mesmo tipo com um nome diferente -
jsonarmazena o texto de entrada exato enquantojsonbarmazena uma forma binária decomposta, e apenasjsonbsuporta operadores de indexação como contenção.
FAQs
Quando devo usar jsonb em vez de colunas com tipos separados?
Use jsonb quando o conjunto de atributos for genuinamente variável ou esparso e você raramente precisar filtrar, unir ou restringir campos individuais, enquanto campos estáveis e frequentemente consultados pertencem a colunas com tipos.
Qual é a diferença real entre json e jsonb?
json armazena o texto de entrada exato, preservando a ordem das chaves e os espaços em branco.
jsonb decompõe o valor em um formato binário, que descarta essa formatação, mas permite indexação e consultas de contenção mais rápidas.
O varchar(n) tem um desempenho melhor que text no PostgreSQL?
Não, pois ambos usam a mesma representação de armazenamento e varchar(n) apenas adiciona uma verificação de comprimento extra quando um valor é escrito.
Por que meu índice não acelera uma consulta que filtra um campo jsonb?
- Um índice B-tree na coluna jsonb inteira não acelera buscas por nível de chave.
- Consultas de contenção com
@>precisam de um índice GIN em vez disso. - Filtrar uma chave bem conhecida geralmente requer um índice de expressão nessa chave, não um índice de coluna inteira.
O que é TOAST e quando ele entra em ação?
TOAST move valores maiores que aproximadamente 2 kilobytes para fora da linha da tabela principal para uma tabela lateral, de modo que valores jsonb ou de texto largos não inchem cada varredura sequencial da tabela.
Por que numeric é preferível a float para dinheiro?
numeric armazena dígitos decimais exatos, enquanto tipos de ponto flutuante aproximam valores em binário e não podem representar a maioria das frações decimais exatamente, o que introduz erro de arredondamento em somas e comparações.
Qual é a diferença entre um tipo enum e uma tabela de consulta?
Um enum é compacto e auto-documentado, mas requer uma migração para adicionar um valor, enquanto uma tabela de consulta com uma chave estrangeira pode ter novos valores sem migração e pode carregar metadados extras por valor.
Como os tipos de intervalo ajudam em comparação com colunas de início/fim separadas?
Um tipo de intervalo representa um lapso como um único valor com operadores de sobreposição e contenção embutidos, o que é mais correto e muitas vezes mais rápido do que comparar duas colunas separadas manualmente.
Uma conversão em uma coluna pode quebrar o uso do índice?
Sim, pois envolver uma coluna indexada em uma conversão ou função, como created_at::date, força o PostgreSQL a avaliar essa expressão por linha em vez de procurar através do índice na coluna bruta.
O que é um domínio e como ele difere de uma restrição CHECK em uma coluna?
Um domínio envolve um tipo base com uma restrição reutilizável que você define uma vez e aplica a várias colunas, de modo que a regra e sua mensagem de erro vivam em um único local em vez de serem repetidas em cada coluna.
Os arrays substituem a necessidade de uma tabela de junção?
Geralmente não, pois arrays funcionam bem para listas pequenas e desnormalizadas como tags, mas uma vez que os elementos do array precisam de seus próprios atributos ou relacionamentos, uma tabela de junção adequada é o melhor ajuste.
Por que alguns tipos precisam de índices GIN ou GiST em vez de B-tree?
Porque B-tree suporta apenas operadores de igualdade e ordenação, enquanto contenção (jsonb, arrays) precisa de GIN e operadores geométricos ou de sobreposição de intervalo precisam de GiST ou SP-GiST, pois estes não são ordenáveis como B-tree exige.
Relacionados
- Noções Básicas de Tipos de Dados - tour prático dos tipos principais
- Operadores e Indexação JSONB - consultas de contenção e índices GIN na prática
- Tipos Numéricos e de Texto - precisão, escala e escolhas de tipos de string
- Arrays e Tipos Compostos - tipos de coleção e coluna estruturada
- Tipos UUID, Enum e Domínio - identificadores, conjuntos fechados e restrições reutilizáveis
- Tipos Date, Time e Timestamptz - escolhas de tipos temporais
Versões da Stack: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17), pgvector 0.8+, e PostGIS 3.5+.