Restrições de chave estrangeira: quando usar ON UPDATE e ON DELETE

196

Estou projetando meu esquema de banco de dados usando o MySQL Workbench, o que é muito legal porque você pode fazer diagramas e os converte: P

Enfim, eu decidi usar o InnoDB por causa do suporte a Chave Estrangeira. Uma coisa que notei, porém, é que ele permite que você defina as opções On Update e Delete para chaves estrangeiras. Alguém pode explicar onde "Restringir", "Cascata" e definir nulo podem ser usados ​​em um exemplo simples?

Por exemplo, digamos que eu tenha uma usertabela que inclua a userID. E digamos que eu tenha uma tabela de mensagens messageque seja muitos para muitos e que tenha duas chaves estrangeiras (que fazem referência à mesma chave primária,userID na usertabela). A configuração das opções Ao atualizar e Ao excluir é útil neste caso? Se sim, qual eu escolho? Se este não for um bom exemplo, você poderia sugerir um bom exemplo para ilustrar como isso pode ser útil?

obrigado

meltuhamy
fonte

Respostas:

485

Não hesite em colocar restrições no banco de dados. Você terá um banco de dados consistente, e esse é um dos bons motivos para usar um banco de dados. Especialmente se você tiver vários aplicativos solicitando-o (ou apenas um aplicativo, mas com um modo direto e um modo em lote usando fontes diferentes).

Com o MySQL, você não possui restrições avançadas, como no postgreSQL, mas pelo menos as restrições de chave estrangeira são bastante avançadas.

Vamos dar um exemplo, uma tabela da empresa com uma tabela do usuário contendo pessoas dessa empresa

CREATE TABLE COMPANY (
     company_id INT NOT NULL,
     company_name VARCHAR(50),
     PRIMARY KEY (company_id)
) ENGINE=INNODB;

CREATE TABLE USER (
     user_id INT, 
     user_name VARCHAR(50), 
     company_id INT,
     INDEX company_id_idx (company_id),
     FOREIGN KEY (company_id) REFERENCES COMPANY (company_id) ON...
) ENGINE=INNODB;

Vamos dar uma olhada na cláusula ON UPDATE :

  • ON UPDATE RESTRICT : o padrão : se você tentar atualizar um company_id na tabela COMPANY, o mecanismo rejeitará a operação se um USUÁRIO, pelo menos, vincular esta empresa.
  • NA ATUALIZAÇÃO SEM AÇÃO : o mesmo que o RESTRITO.
  • NO CASO DE ATUALIZAÇÃO : o melhor normalmente : se você atualizar um company_id em uma linha da tabela COMPANY, o mecanismo o atualizará de acordo com todas as linhas do USUÁRIO que fazem referência a essa EMPRESA (mas nenhum gatilho é ativado na tabela USER, aviso). O mecanismo acompanhará as alterações para você, é bom.
  • ON UPDATE SET NULL : se você atualizar um company_id em uma linha da tabela COMPANY, o mecanismo definirá USERs relacionados company_id como NULL (deve estar disponível no campo USER company_id). Não consigo ver nada de interessante em uma atualização, mas posso estar errado.

E agora no lado ON DELETE :

  • EM EXCLUIR RESTRITIVO : o padrão : se você tentar excluir um ID de company_id na tabela COMPANY, o mecanismo rejeitará a operação se um USUÁRIO, pelo menos, vincular esta empresa, poderá salvar sua vida.
  • EM EXCLUIR SEM AÇÃO : o mesmo que RESTRITO
  • EM EXCLUIR CASCADE : perigoso : se você excluir uma linha da empresa na tabela EMPRESA, o mecanismo excluirá também os USUÁRIOS relacionados. Isso é perigoso, mas pode ser usado para fazer limpezas automáticas em tabelas secundárias (portanto, pode ser algo que você deseja, mas certamente não para um exemplo de EMPRESA <-> USER)
  • ON DELETE SET NULL : punhado : se você excluir uma linha da EMPRESA, os USUÁRIOS relacionados terão automaticamente o relacionamento com NULL. Se Nulo é o seu valor para usuários sem empresa, isso pode ser um bom comportamento, por exemplo, talvez você precise manter os usuários em seu aplicativo, como autores de algum conteúdo, mas remover a empresa não é um problema para você.

geralmente o meu padrão é: ON DELETE RESTRICT ON UPDATE CASCADE . com algumas ON DELETE CASCADEpara tabelas de rastreamento (logs - nem todos os logs--, coisas assim) e ON DELETE SET NULLquando a tabela principal é um 'atributo simples' para a tabela que contém a chave estrangeira, como uma tabela JOB para a tabela USER.

Editar

Faz muito tempo desde que escrevi isso. Agora acho que devo adicionar um aviso importante. O MySQL tem uma grande limitação documentada com cascatas. Cascatas não estão disparando gatilhos . Portanto, se você estava confiante o suficiente nesse mecanismo para usar gatilhos, deve evitar restrições em cascata.

Os gatilhos do MySQL são ativados apenas para alterações feitas nas tabelas por instruções SQL. Eles não são ativados para mudanças nas visualizações, nem nas tabelas feitas pelas APIs que não transmitem instruções SQL para o MySQL Server

==> Veja abaixo a última edição, as coisas estão mudando neste domínio

Os gatilhos não são ativados por ações de chave estrangeira.

E eu não acho que isso será corrigido um dia. As restrições de chave estrangeira são gerenciadas pelo armazenamento InnoDb e os Triggers são gerenciados pelo mecanismo SQL do MySQL. Ambos são separados. O Innodb é o único armazenamento com gerenciamento de restrições; talvez eles adicionem gatilhos diretamente no mecanismo de armazenamento um dia, talvez não.

Mas tenho minha própria opinião sobre qual elemento você deve escolher entre a implementação deficiente do acionador e as restrições de chaves estrangeiras muito úteis. E quando você se acostumar com a consistência do banco de dados, vai adorar o PostgreSQL.

12/2017-Atualizando esta edição sobre MySQL:

como afirma @IstiaqueAhmed nos comentários, a situação mudou neste assunto. Portanto, siga o link e verifique a situação real atualizada (que pode mudar novamente no futuro).

regilero
fonte
8
ON DELETE CASCADE : dangerous- tome com uma pitada de sal.
onedaywhen
3
Você precisará ter cuidado com a cascata, pois pode bloquear o sistema se muitos registros precisarem ser alterados. A exclusão em cascata deve ser examinada com cuidado antes de usar, geralmente você realmente deseja que a exclusão não ocorra se houver registros filhos. Eu não gostaria que um cliente fosse excluído para limpar os dados financeiros dos oreders que ele possuía anteriormente. Às vezes, é melhor garantir que o cascading não esteja ativado e fornecer uma maneira de amrk registros como inativos.
HLGEM 14/10
1
Em termos da lógica de negócios, há um caso que pode ser interessante SET NULLem ON UPDATE: atualizar uma empresa representa um desapego da relação Empresa> Usuário. Por exemplo: se uma empresa altera seu tipo de negócio, os usuários anteriores podem não estar mais relacionados a esse negócio, portanto, NULLpode ser preferível para esse índice.
precisa saber é o seguinte
1
@regilero, parece que o conteúdo do seu primeiro link ( dev.mysql.com/doc/refman/5.6/en/triggers.html ) para o site mysql foi alterado. Diz em This includes changes to base tables that underlie updatable viewsvez do que você colou, ou seja,They do not activate for changes in views
Istiaque Ahmed
6
"Eu não gostaria que um cliente excluísse os dados financeiros dos pedidos que ele tinha anteriormente". Em uma situação como essa, você provavelmente ainda precisará dos dados do cliente. Seu design provavelmente deve estar marcando o cliente como inativo, não excluindo sua linha do banco de dados. Na prática, tem sido a minha experiência profissional que você realmente muito raramente deseja excluir nada , preferindo marcar inativa por padrão. Nos casos em que a exclusão permanente é aceitável, CASCADE DELETEgeralmente também é aceitável, até preferida. Não considero isso particularmente perigoso.
GrandOpener 08/02/19
3

Além da resposta do @MarkR - uma coisa a ser observada seria que muitas estruturas PHP com ORMs não reconheceriam ou usariam a configuração avançada do banco de dados (chaves estrangeiras, exclusão em cascata, restrições exclusivas) e isso pode resultar em comportamento inesperado.

Por exemplo, se você excluir um registro usando ORM, e você DELETE CASCADEexcluirá registros em tabelas relacionadas, a tentativa do ORM de excluir esses registros relacionados (geralmente automáticos) resultará em erro.

lxa
fonte
11
Isso seria motivo para não usar esse ORM específico. Qualquer ferramenta que é tão ruim no suporte ao banco de dados não é confiável. Chaves estrangeiras e exclusões ou atualizações em cascata são conceitos básicos de banco de dados, não conceitos avançados e nenhum banco de dados real deve ser projetado sem restrições de chave estrangeira!
HLGEM 14/10
O problema é que eles lançam erros. É possível RESTRIIR RESTRIÇÕES, mas o mecanismo não gera erros, mas ainda mantém a semântica? Gostaria que meu programa continuasse e, ao mesmo tempo, protegesse outros dados da exclusão.
TheRealChx101
2

Você precisará considerar isso no contexto do aplicativo. Em geral, você deve criar um aplicativo, não um banco de dados (o banco de dados simplesmente fazendo parte do aplicativo).

Considere como seu aplicativo deve responder a vários casos.

A ação padrão é restringir (ou seja, não permitir) a operação, que normalmente é o que você deseja, pois evita erros estúpidos de programação. No entanto, no DELETE CASCADE também pode ser útil. Realmente depende do seu aplicativo e de como você deseja excluir objetos específicos.

Pessoalmente, eu usaria o InnoDB porque ele não lixeira seus dados (cf MyISAM, o que faz), e não porque ele tem restrições de FK.

MarkR
fonte