Para responder adequadamente a essa pergunta, primeiro você precisa decidir: O que "excluir" significa no contexto deste sistema / aplicativo?
Para responder a essa pergunta, você precisa responder a outra pergunta: Por que os registros estão sendo excluídos?
Existem várias boas razões pelas quais um usuário pode precisar excluir dados. Normalmente, acho que há exatamente uma razão (por tabela) para a exclusão de uma exclusão. Alguns exemplos são:
- Recuperar espaço em disco;
- É necessária a exclusão definitiva de acordo com a política de retenção / privacidade;
- Dados corrompidos / irremediavelmente incorretos, mais fáceis de excluir e regenerar do que reparar.
- A maioria das linhas é excluída, por exemplo, uma tabela de log limitada a X registros / dias.
Há também algumas razões muito ruins para exclusão completa (mais sobre as razões para isso posteriormente):
- Para corrigir um erro menor. Isso geralmente ressalta a preguiça do desenvolvedor e uma interface host.
- Anular uma transação (por exemplo, fatura que nunca deveria ter sido faturada).
- Porque você pode .
Por que, você pergunta, é realmente tão importante? O que há de errado com o bom e velho DELETE
?
- Em qualquer sistema que esteja remotamente vinculado ao dinheiro, a exclusão definitiva viola todos os tipos de expectativas contábeis, mesmo se movida para uma tabela de arquivamento / marca para exclusão. A maneira correta de lidar com isso é um evento retroativo .
- As tabelas de arquivamento tendem a divergir do esquema ativo. Se você esquecer apenas uma coluna ou cascata recém-adicionada, você perderá esses dados permanentemente.
- A exclusão definitiva pode ser uma operação muito cara, especialmente com cascatas . Muitas pessoas não percebem que a cascata mais de um nível (ou em alguns casos, qualquer cascata, dependendo DBMS) irá resultar em operações de nível recorde em vez de operações de conjunto.
- A exclusão intensa e repetida acelera o processo de fragmentação do índice.
Então, a exclusão suave é melhor, certo? Não, na verdade não:
- Configurar cascatas se torna extremamente difícil. Você quase sempre acaba com o que aparece para o cliente como linhas órfãs.
- Você só consegue rastrear uma exclusão. E se a linha for excluída e desmarcada várias vezes?
- O desempenho da leitura sofre, embora isso possa ser atenuado de alguma forma com particionamento, visualizações e / ou índices filtrados.
- Como sugerido anteriormente, pode ser ilegal em alguns cenários / jurisdições.
A verdade é que ambas as abordagens estão erradas. A exclusão está errada. Se você está realmente fazendo essa pergunta, significa que está modelando o estado atual em vez das transações. Essa é uma prática ruim e ruim na área de banco de dados.
Udi Dahan escreveu sobre isso em Não Excluir - Apenas Não . Há sempre algum tipo de tarefa, operação, atividade , ou (o meu preferido prazo) evento que realmente representa o "delete". Tudo bem se você desejar desnormalizar posteriormente em uma tabela de "estado atual" para desempenho, mas faça isso depois de definir o modelo transacional, não antes.
Nesse caso, você tem "usuários". Usuários são essencialmente clientes. Os clientes têm um relacionamento comercial com você. Esse relacionamento não simplesmente desaparece no ar porque eles cancelaram sua conta. O que realmente está acontecendo é:
- Cliente cria conta
- O cliente cancela a conta
- Cliente renova conta
- O cliente cancela a conta
- ...
Em todos os casos, é o mesmo cliente e, possivelmente, a mesma conta (ou seja, cada renovação de conta é um novo contrato de serviço). Então, por que você está excluindo linhas? Isso é muito fácil de modelar:
+-----------+ +-------------+ +-----------------+
| Account | --->* | Agreement | --->* | AgreementStatus |
+-----------+ +-------------+ +----------------+
| Id | | Id | | AgreementId |
| Name | | AccountId | | EffectiveDate |
| Email | | ... | | StatusCode |
+-----------+ +-------------+ +-----------------+
É isso aí. É tudo o que há para isso. Você nunca precisa excluir nada. O acima é um design bastante comum que acomoda um bom grau de flexibilidade, mas você pode simplificá-lo um pouco; você pode decidir que não precisa do nível "Contrato" e fazer com que "Conta" vá para uma tabela "Status da conta".
Se uma necessidade frequente em seu aplicativo é obter uma lista de contratos / contas ativos , é uma consulta (um pouco) complicada, mas é para isso que servem as visualizações:
CREATE VIEW ActiveAgreements AS
SELECT agg.Id, agg.AccountId, acc.Name, acc.Email, s.EffectiveDate, ...
FROM AgreementStatus s
INNER JOIN Agreement agg
ON agg.Id = s.AgreementId
INNER JOIN Account acc
ON acc.Id = agg.AccountId
WHERE s.StatusCode = 'ACTIVE'
AND NOT EXISTS
(
SELECT 1
FROM AgreementStatus so
WHERE so.AgreementId = s.AgreementId
AND so.EffectiveDate > s.EffectiveDate
)
E você terminou. Agora você tem algo com todos os benefícios das exclusões eletrônicas, mas nenhuma das desvantagens:
- Os registros órfãos não são um problema porque todos os registros são visíveis o tempo todo; basta selecionar a partir de uma visualização diferente sempre que necessário.
- "Excluir" geralmente é uma operação incrivelmente barata - basta inserir uma linha em uma tabela de eventos.
- Nunca há qualquer chance de perder qualquer história, sempre , não importa o quanto você estragar.
- Você ainda pode excluir uma conta manualmente, se precisar (por exemplo, por motivos de privacidade), e ficar à vontade com o conhecimento de que a exclusão ocorrerá de forma limpa e não interferirá em nenhuma outra parte do aplicativo / banco de dados.
O único problema a ser resolvido é o problema de desempenho. Em muitos casos, na verdade, não é um problema por causa do índice clusterizado AgreementStatus (AgreementId, EffectiveDate)
- há muito pouca procura de E / S lá. Mas, se houver algum problema, há maneiras de resolver isso, usando gatilhos, visualizações indexadas / materializadas, eventos no nível do aplicativo, etc.
Porém, não se preocupe com o desempenho muito cedo - é mais importante acertar o design e, "nesse caso", significa usar o banco de dados da maneira que um banco de dados deve ser usado, como um sistema transacional .