CommunityPesquisa e Análise de Dadosgithub.com

Миграции PostgreSQL

SQL-миграции PostgreSQL: парные up/down-файлы, современные типы и ограничения, откат каждой операции, неблокирующие индексы, требования к мигратору.

O que é Миграции PostgreSQL?

Este skill define um padrão robusto e seguro para gerenciar migrações de banco de dados PostgreSQL. Ele exige que cada migração seja um par de arquivos `up` e `down`, onde o `down` reverte completamente o `up`. A migração é executada em uma única transação, com a única exceção sendo a criação de índices em tabelas ao vivo com `CREATE INDEX CONCURRENTLY`, que deve ser feita em uma migração separada e sem transação. O skill também fornece diretrizes claras sobre tipos de dados modernos (como `bigint GENERATED ALWAYS AS IDENTITY` em vez de `bigserial`, `text` com `CHECK` para limites de comprimento, `timestamptz` para timestamps e `jsonb` para dados JSON), reversão de cada operação (incluindo adição de valores a enums e criação de índices), e solução de problemas como índices válidos que não são usados pelo planejador de consultas (`indcheckxmin = true`). Finalmente, ele especifica requisitos rigorosos para a ferramenta de migração, incluindo suporte a transações, bloqueio contra execução paralela e um modo sem transação para migrações de índice. O skill suporta qualquer plataforma de agente de IA que execute comandos SQL em PostgreSQL.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/scary-production/claude-plugins/tree/HEAD/plugins/go-postgres/skills/postgres-migrations

Installed? Explore more Pesquisa e Análise de Dados skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Migrações PostgreSQL

Principal. Cada migração é um par up/down, e o down reverte completamente o up. A migração é executada em sua própria transação. Há uma exceção: índice em uma tabela que já possui dados e está recebendo escritas — CREATE INDEX CONCURRENTLY em uma migração separada sem transação, um comando por arquivo.

Verificação de afirmações — CHECKS.md.

Formato dos arquivos

migrations/
├── 20250524120000_create_users.up.sql
├── 20250524120000_create_users.down.sql
├── 20250525090000_add_users_phone.up.sql
└── 20250525090000_add_users_phone.down.sql
  • Prefixo — hora UTC YYYYMMDDHHmmss, nome — descrição curta em snake_case.
  • *.up.sql aplica, *.down.sql reverte. Ambos os arquivos são obrigatórios.
  • Não edite uma migração já aplicada — escreva uma nova.

Tipos e restrições

Em vez deEscrevaPor quê
bigserialbigint GENERATED ALWAYS AS IDENTITYpadrão SQL; BY DEFAULT se precisar inserir seus próprios ids (importação)
varchar(n)text NOT NULL CHECK (length(x) <= n)limite é uma regra de dados, mais fácil de alterar
timestamptimestamptzarmazena um momento no tempo, não "horas na parede"
flag integerboolean
jsonjsonbformato binário, índices e operadores
uuid de extensãouuid DEFAULT gen_random_uuid()embutido desde PostgreSQL 13

Limite o comprimento de colunas de texto com CHECK sempre, exceto para texto sabidamente longo (descrição, comentário). Se o comprimento máximo não for óbvio, pergunte ao usuário.

DadosRestrição
emailCHECK (length(email) <= 320)
nomeCHECK (length(name) <= 255)
telefoneCHECK (length(phone) <= 50)
slug, códigoCHECK (length(code) <= 100)
URLCHECK (length(url) <= 2048)
descriçãosem restrição ou CHECK (length(bio) <= 10000)

Modelo de tabela:

CREATE TABLE users (
    id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name       text        NOT NULL CHECK (length(name) <= 255),
    email      text        NOT NULL CHECK (length(email) <= 320),
    is_active  boolean     NOT NULL DEFAULT true,
    metadata   jsonb,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now()
);

CREATE UNIQUE INDEX ix_users_email ON users (email);

Reversão

updown
CREATE TABLE tDROP TABLE IF EXISTS t
ALTER TABLE t ADD COLUMN cALTER TABLE t DROP COLUMN IF EXISTS c
CREATE INDEX ixDROP INDEX IF EXISTS ix
CREATE INDEX CONCURRENTLY ixDROP INDEX CONCURRENTLY IF EXISTS ix — também sem transação
CREATE TYPE t AS ENUM (...)DROP TYPE IF EXISTS t
ALTER TYPE t ADD VALUE 'v'DROP VALUE não existe no PostgreSQL — recriação do tipo ou down vazio
ALTER TABLE t ALTER COLUMN c SET NOT NULLALTER TABLE t ALTER COLUMN c DROP NOT NULL
ALTER TABLE t ADD CONSTRAINT cALTER TABLE t DROP CONSTRAINT IF EXISTS c

IF EXISTS no down — para que a reversão seja idempotente.

Alterações típicas

Coluna com default. Com DEFAULT constante a tabela não é reescrita (PostgreSQL 11+), o comando é instantâneo. Com default volátil (gen_random_uuid(), clock_timestamp()) a tabela é reescrita por inteiro sob bloqueio exclusivo — em uma tabela grande e ativa, adicione a coluna sem default e preencha em lotes.

ALTER TABLE users ADD COLUMN role text NOT NULL DEFAULT 'user' CHECK (length(role) <= 50);

Chave estrangeira — crie imediatamente um índice na coluna referenciadora: o PostgreSQL não o cria automaticamente, e sem ele a exclusão do pai e os joins são feitos por varredura total.

CREATE TABLE orders (
    id      bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    user_id bigint NOT NULL REFERENCES users (id),
    total   numeric NOT NULL
);
CREATE INDEX ix_orders_user_id ON orders (user_id);

Valor em enum. ALTER TYPE ... ADD VALUE funciona dentro de uma transação, mas o novo valor não pode ser usado até o commit (55P04 unsafe use of new value). Se o migrador executa a migração em uma transação, adicione o valor e use-o (UPDATE, DEFAULT, CHECK) em migrações diferentes.

ALTER TYPE payment_method ADD VALUE 'bank_transfer';

down: DROP VALUE não existe. O valor é inofensivo — deixe o down vazio com um comentário; se não for, recrie o tipo (novo tipo, ALTER COLUMN ... TYPE ... USING, remoção do antigo).

Índices em tabelas ativas

Tabela já com dados e recebendo escritas — índice via CREATE INDEX CONCURRENTLY (CIC). O CREATE INDEX comum bloqueia escritas durante toda a construção. Índice junto com nova tabela na mesma migração — CREATE INDEX comum. O tamanho do banco não salva: uma transação pendente quebra o novo índice em qualquer tamanho (veja abaixo).

up:

CREATE INDEX CONCURRENTLY ix_orders_customer_id ON orders (customer_id);

down:

DROP INDEX CONCURRENTLY IF EXISTS ix_orders_customer_id;
  • Migração separada sem transação, um comando por arquivo. Dentro de um bloco de transação, CIC é proibido ("cannot run inside a transaction block"). Vários comandos enviados em uma única requisição são executados pelo PostgreSQL como uma transação implícita, e o CIC falha com o mesmo erro.
  • Se o migrador não suporta migração sem transação — não coloque CIC em uma migração normal. Diga ao usuário que esse modo é necessário (seção "O que exigir do migrador"), ou execute o CIC separadamente e registre a migração manualmente.
  • CIC aguarda todas as transações com snapshot antigo, mesmo aquelas que leem outras tabelas. A migração pode demorar.
  • Após aplicar, verifique o índice. CIC não é transacional: em caso de falha, um índice INVALID permanece, e a própria migração não mostrará isso.
SELECT c.relname, i.indisvalid, i.indcheckxmin
FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname = 'ix_orders_customer_id';
  • indisvalid = false — DROP INDEX CONCURRENTLY IF EXISTS e recrie, ou REINDEX INDEX CONCURRENTLY.
  • Reexecução de migração CIC que falhou. Sem IF NOT EXISTS, ela falhará com "relation already exists" no índice INVALID. Com IF NOT EXISTS — pior: o comando silenciosamente ignorará o índice INVALID, e a migração "passará". Portanto, não escreva IF NOT EXISTS no CIC: primeiro remova o índice inválido, depois repita.

O índice existe, mas o plano é Seq Scan

O índice é válido, mas o planejador não o usa — verifique indcheckxmin (consulta acima). true: as consultas não usam o índice enquanto existirem transações iniciadas antes de sua criação. A causa é UPDATE: na tabela restaram cadeias de versões HOT, para as quais o índice não faz referência. Apenas INSERTs não causam isso. Não recrie o índice nem procure a causa nas estatísticas. Encontre transações antigas:

SELECT pid, xact_start, backend_xmin, state, left(query, 60) AS query
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY xact_start;

Aguarde sua conclusão ou encerre-as (pg_terminate_backend(pid)) — somente após acordo com o usuário. Para reproduzir antes do lançamento: em uma sessão, uma transação aberta repeatable read com leitura da tabela; em outra, UPDATE de linhas e CREATE INDEX; indcheckxmin se tornará true.

O que exigir do migrador

  • Cada migração — sua própria transação, junto com a gravação no log de aplicadas: uma migração que falha é revertida por completo, as já aplicadas permanecem.
  • Bloqueio contra execução paralela (por exemplo, advisory lock): réplicas que iniciarem juntas não aplicarão a mesma migração duas vezes.
  • Modo sem transação para migrações individuais — uma marcação no arquivo (por exemplo, primeira linha -- migrate:no-transaction). Esse arquivo é enviado como um único comando, fora da transação. Custo: a aplicação e o registro no log não são mais atômicos. Se o processo falhar entre eles, a repetição falhará com "already exists"; verifique indisvalid; se o índice for válido, registre a migração no log manualmente.
  • Criação do par de arquivos sem conexão com o banco.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

scary-production/claude-plugins

Работа с PostgreSQL в Go через pgxpool (pgx v5) и avito-tech/go-transaction-manager: пул и устойчивый старт, структуры с тегами db и CollectRows, репозиторий через DefaultTrOrDB, транзакции через Do, propagation и savepoint, повтор транзакции при 40001/40P01/55P03, защита от SQL-инъекций, классификация ошибок и ON CONFLICT, batch и CopyFrom. Конкурентная запись и блокировки — скилл postgres-concurrency. PostgreSQL in Go via pgxpool and go-transaction-manager: pool, repositories, transactions, retries, errors, bulk loads.

scary-production/claude-plugins

Конкурентная запись в PostgreSQL без потерянных обновлений и write skew: условие в UPDATE, SELECT FOR NO KEY UPDATE против FOR UPDATE, exclusion-ограничения против пересечения интервалов, serializable и повтор при 40001/40P01/55P03, очередь задач на SKIP LOCKED, lock_timeout и NOWAIT, advisory-блокировки, разбор зависаний. Примеры — Go (pgx и go-transaction-manager), правила — про сам PostgreSQL. PostgreSQL concurrency: lost updates, write skew, row locks, exclusion constraints, isolation and retries, SKIP LOCKED queues, advisory locks.

scary-production/claude-plugins

Как найти, что тормозит в PostgreSQL, и доказать, что правка помогла. Профиль по pg_stat_statements, сравнение вариантов по числу страниц (BUFFERS), а не по секундам, разбор плана от места, где оценка строк впервые разошлась с фактом. N+1 из Go-цикла сворачиваем в один запрос. Правим условия, FK, статистику и память, а не боремся с планировщиком. Postgres query tuning: profile first, compare by buffers, fix row estimates, remove N+1.

scary-production/claude-plugins

Показывает стили строки состояния на примере или включает один из них (full, compact, bars, minimal) в настройках пользователя или проекта.

Habilidades Relacionadas