DDL Declarativo de Particionamento
Tabelas pai/filho e exclusão de constraints permitem que o PostgreSQL direcione inserções e poda scans sem roteamento manual baseado em triggers.
Receita
Cartão de referência rápida - pronto para copiar e colar.
CREATE TABLE measurements (
sensor_id integer NOT NULL,
measured_at timestamptz NOT NULL,
value double precision NOT NULL,
PRIMARY KEY (sensor_id, measured_at)
) PARTITION BY RANGE (measured_at);
CREATE TABLE measurements_2026_q1 PARTITION OF measurements
FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
CREATE TABLE measurements_2026_q2 PARTITION OF measurements
FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');
ALTER TABLE measurements ADD CONSTRAINT measurements_value_check
CHECK (value >= 0);Quando usar isso:
- Tabelas que excedem janelas gerenciáveis de vacuum/backup como um único heap.
- Consultas que filtram consistentemente em uma chave de partição (tempo, tenant, região).
- Política de retenção que remove ou arquiva janelas de tempo inteiras.
Exemplo de Trabalho
BEGIN;
CREATE TABLE audit_log (
log_id bigint GENERATED ALWAYS AS IDENTITY,
tenant_id bigint NOT NULL,
logged_at timestamptz NOT NULL DEFAULT now(),
action text NOT NULL,
details jsonb,
PRIMARY KEY (log_id, tenant_id, logged_at)
) PARTITION BY RANGE (logged_at);
CREATE TABLE audit_log_2026_h1 PARTITION OF audit_log
FOR VALUES FROM ('2026-01-01') TO ('2026-07-01');
CREATE TABLE audit_log_2026_h2 PARTITION OF audit_log
FOR VALUES FROM ('2026-07-01') TO ('2027-01-01');
CREATE INDEX audit_log_tenant_logged_idx
ON audit_log (tenant_id, logged_at DESC);
-- Inserção é roteada automaticamente para a partição correta
INSERT INTO audit_log (tenant_id, action, details)
VALUES (42, 'login', '{"ip": "10.0.0.1"}');
-- Inspecionar a árvore de partições
SELECT inhrelid::regclass AS partition
FROM pg_inherits
WHERE inhparent = 'audit_log'::regclass;
COMMIT;O que isso demonstra:
PARTITION BYdeclarativo na tabela pai com filhos definidos por limites.- PK composto inclui colunas da chave de partição (
logged_at). - Índices criados na tabela pai existem em cada partição.
Mergulho Profundo
Como Funciona
- A tabela pai é uma tabela particionada - inserções e consultas visam a pai; o planejador expande para os filhos.
- Cada filho herda as definições de coluna; os limites são armazenados como constraints de partição.
- Exclusão de constraints (poda de partições) pula filhos cujos limites não podem corresponder ao predicado da consulta.
- A sintaxe
ONLY parentaborda a tabela pai sem filhos para casos de borda de DDL.
Operações de DDL
| Operação | Propósito |
|---|---|
CREATE TABLE ... PARTITION OF | Adicionar filho com limites |
ATTACH PARTITION | Promover tabela existente para filho |
DETACH PARTITION | Remover filho como tabela independente |
SPLIT PARTITION | (não embutido) - usar detach + novos filhos |
Partição DEFAULT | Capturar inserções não correspondentes |
Notas de SQL
-- Template para automação mensal
DO $$
DECLARE
start_date date := date_trunc('month', now())::date;
end_date date := (start_date + interval '1 month')::date;
part_name text := 'events_' || to_char(start_date, 'YYYY_MM');
BEGIN
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %I PARTITION OF events FOR VALUES FROM (%L) TO (%L)',
part_name, start_date, end_date
);
END;
$$;Armadilhas
- PK sem chave de partição - DDL falha em tabelas particionadas. Correção: incluir coluna de partição na PK e em constraints UNIQUE.
- Limites sobrepostos - attach falha ou causa roteamento ambíguo. Correção: intervalos semiabertos, criação automatizada de partições.
- Muitas partições - milhares de partições diárias prejudicam o planejamento. Correção: partições semanais/mensais; veja as melhores práticas.
- UNIQUE global sem chave de partição - não suportado entre partições. Correção: incluir chave de partição ou usar ID substituto por escopo de partição.
- Trigger FOR EACH ROW apenas na pai - o comportamento difere de tabelas não particionadas. Correção: testar triggers em cada filho ou usar lógica de aplicação.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Tabela grande única + BRIN | Time-series moderado, operações simples | Necessita de detach rápido para retenção de partição |
| Sharding externo ao PG | Escala de escrita além de um nó | Transações entre shards são necessárias |
| Herança de tabela (legado) | Manutenção de código PG antigo | Greenfield - usar declarativo |
| Tabela separada por mês (manual) | Operações customizadas extremas | Deseja superfície unificada de DDL e consulta |
FAQs
Os dados ficam na tabela pai?
Não. As linhas ficam nas partições filhas. A tabela pai é um invólucro de roteamento, exceto brevemente durante algumas operações de attach.
Chaves estrangeiras podem referenciar uma tabela particionada?
Sim, no PostgreSQL 18 - FKs podem referenciar tabelas pai particionadas. FKs de tabelas particionadas referenciando tabelas não particionadas também são suportadas com ressalvas em ON DELETE entre partições.
Quantas colunas de chave de partição posso usar?
Tipicamente uma coluna para range/list/hash. Particionamento range multi-coluna é suportado com listas de colunas nas definições de limites.
O que é exclusão de constraints?
O planejador pula partições cujos constraints de verificação contradizem a cláusula WHERE da consulta. É o mecanismo por trás da poda de partições.
Posso mudar a estratégia de partição depois?
Não no local. Crie uma nova tabela particionada, copie/troque, ou anexe a estratégia com uma janela de migração.
As partições precisam ter padrões de coluna correspondentes?
Filhos herdam padrões da pai na criação. Altere os padrões da pai e propague cuidadosamente para novos filhos.
Como funcionam as colunas GENERATED?
Defina na pai; os filhos herdam. Colunas de identidade funcionam por partição com GENERATED BY DEFAULT para fluxos de trabalho de attach.
E sobre sub-particionamento?
O PostgreSQL suporta hierarquias de partições (por exemplo, range por mês, sub-partição hash por tenant). Adiciona complexidade de planejamento - use quando comprovadamente necessário.
Tabelas temporárias são particionáveis?
Não. O particionamento visa tabelas base persistentes para benefícios de retenção e poda.
Como listar os limites das partições?
SELECT c.relname, pg_get_expr(c.relpartbound, c.oid)
FROM pg_class c
JOIN pg_inherits i ON i.inhrelid = c.oid
WHERE i.inhparent = 'audit_log'::regclass;Relacionado
- Noções Básicas de Particionamento - introdução a range/list/hash
- Poda de Partições - comportamento do planejador
- Retenção e Detach - gerenciamento de ciclo de vida
- Armadilhas de Particionamento - erros comuns
Versões da Pilha: Esta página foi escrita para PostgreSQL 18.4 (estável 18, manutenção 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, e PostGIS 3.5+.