sqlpostgresqlforeign-keycascade

FOREIGN KEY ON DELETE: CASCADE, SET NULL e RESTRICT na pratica

O que acontece com as linhas filhas ao apagar um pai e como escolher entre CASCADE, SET NULL, RESTRICT e NO ACTION sem perder dados.

3 min de leituraReferencesql · postgresql · foreign-key · cascade · constraints · referential-integrity

Uma chave estrangeira faz mais do que marcar uma relacao entre tabelas: ela dita o que o banco faz com as linhas filhas quando voce apaga o pai. Essa reacao se chama acao referencial e voce a escolhe com a clausula ON DELETE. Escolha errado e voce ou recebe um erro a cada delete ou perde dados em silencio.

Um esquema base e um vinculo

Pegue tres tabelas conhecidas. orders tem uma chave estrangeira para users, e employees referencia a si mesma por meio de manager_id.

CREATE TABLE users (
    id         bigint PRIMARY KEY,
    email      text UNIQUE NOT NULL,
    name       text,
    country    text,
    created_at timestamptz DEFAULT now()
);

CREATE TABLE orders (
    id         bigint PRIMARY KEY,
    user_id    bigint NOT NULL,
    amount     numeric(12,2),
    status     text,
    created_at timestamptz DEFAULT now(),
    CONSTRAINT fk_orders_user
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

Agora DELETE FROM users WHERE id = 7 remove automaticamente todos os pedidos daquele usuario. Sem a clausula ON DELETE, a acao padrao e NO ACTION: apagar um pai que ainda tem filhos simplesmente falha com um erro.

As quatro acoes de exclusao

  • CASCADE apaga as linhas filhas junto com o pai. Otimo para dados estritamente dependentes (itens dentro de um pedido), perigoso para qualquer coisa com valor proprio.
  • SET NULL zera a referencia na linha filha. A coluna FK precisa aceitar nulos. Util quando o filho sobrevive ao pai: voce apaga o usuario mas mantem os pedidos com user_id = NULL.
  • RESTRICT impede apagar o pai enquanto existir alguma linha filha. A verificacao dispara de imediato.
  • NO ACTION e o mesmo comportamento padrao, mas a verificacao pode ser adiada para o fim da transacao em uma restricao deferrable.
-- orders keep living, the link is cleared
ALTER TABLE orders
    DROP CONSTRAINT fk_orders_user,
    ADD CONSTRAINT fk_orders_user
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;

Existe tambem o ON UPDATE: as mesmas acoes disparam quando a propria chave primaria do pai muda. Na pratica voce quase nunca altera uma PK, entao ON UPDATE CASCADE raramente e necessario.

Quando o cascade e perigoso

ON DELETE CASCADE e tentador porque "tudo se limpa sozinho". Mas ele tambem apaga dados sem confirmacao e sem rastro na aplicacao.

-- one innocent-looking statement
DELETE FROM users WHERE country = 'RU';
-- and every order of every matching user is gone too

Pegadinha: os cascades encadeiam. Se orders tem uma filha order_items tambem com CASCADE, apagar um usuario varre tres niveis. Em escala isso e uma transacao longa e bloqueante e um WAL inflado. Para dados financeiros ou de auditoria prefira RESTRICT ou um soft delete (deleted_at), e deixe o DELETE real para rotinas de limpeza.

Indexar a coluna da chave estrangeira

O PostgreSQL indexa automaticamente a chave primaria para a qual a FK aponta, mas NAO cria um indice na coluna que referencia. Sem ele, cada exclusao ou atualizacao de um pai dispara um scan sequencial da tabela filha para encontrar as linhas dependentes.

CREATE INDEX idx_orders_user_id ON orders (user_id);

Isso importa principalmente com CASCADE e SET NULL, porque a verificacao dos filhos roda a cada exclusao do pai. A mesma regra vale para a tabela autorreferenciada:

CREATE INDEX idx_employees_manager ON employees (manager_id);

Restricoes adiaveis e autorreferencias

As vezes voce precisa quebrar a integridade temporariamente dentro de uma transacao, por exemplo para trocar linhas de lugar ou carregar dados com referencias circulares. Para isso voce declara a restricao DEFERRABLE:

CREATE TABLE employees (
    id         bigint PRIMARY KEY,
    name       text,
    manager_id bigint,
    dept       text,
    salary     numeric(12,2),
    CONSTRAINT fk_emp_manager
        FOREIGN KEY (manager_id) REFERENCES employees(id)
        ON DELETE SET NULL
        DEFERRABLE INITIALLY DEFERRED
);

Com INITIALLY DEFERRED a verificacao e adiada ate o COMMIT, entao voce pode manter referencias penduradas dentro da transacao. Lembre das diferencas entre os bancos:

  • MySQL (InnoDB) suporta CASCADE, SET NULL, RESTRICT e NO ACTION, mas nao tem DEFERRABLE: toda verificacao e imediata.
  • ClickHouse nao tem chaves estrangeiras: a integridade e tarefa da aplicacao e as relacoes sao modeladas com JOIN.

A regra base: cascade para dados estritamente dependentes, SET NULL para filhos que sobrevivem ao pai, RESTRICT para tudo que tem valor, e sempre indexe a coluna FK.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador