Per rispondere correttamente a questa domanda devi prima decidere: cosa significa "cancellare" nel contesto di questo sistema / applicazione?
Per rispondere a questa domanda, devi rispondere a un'altra domanda: perché i record vengono eliminati?
Esistono diversi buoni motivi per cui un utente potrebbe dover eliminare i dati. Di solito trovo che ci sia esattamente un motivo (per tabella) per cui potrebbe essere necessaria una cancellazione. Alcuni esempi sono:
- Per recuperare spazio su disco;
- Eliminazione forzata richiesta secondo la politica di conservazione / privacy;
- Dati danneggiati / irrimediabilmente errati, più facili da eliminare e rigenerare che da riparare.
- La maggior parte delle righe viene eliminata, ad esempio una tabella di registro limitata a X record / giorni.
Ci sono anche alcuni motivi molto scarsi per l'eliminazione definitiva (più sui motivi per questi in seguito):
- Per correggere un errore minore. Questo di solito sottolinea la pigrizia degli sviluppatori e un'interfaccia utente ostile.
- Annullare una transazione (ad es. Fattura che non avrebbe mai dovuto essere fatturata).
- Perché puoi .
Perché, chiedi, è davvero un grosso problema? Cosa c'è che non va nel good ole ' DELETE
?
- In qualsiasi sistema anche in remoto legato al denaro, l'eliminazione forzata viola ogni tipo di aspettativa contabile, anche se spostata su un tavolo di archivio / lapide. Il modo corretto di gestirlo è un evento retroattivo .
- Le tabelle di archivio hanno la tendenza a divergere dallo schema live. Se ti dimentichi anche di una sola colonna o cascata appena aggiunta, hai perso definitivamente quei dati.
- La cancellazione forzata può essere un'operazione molto costosa, specialmente con le cascate . Molte persone non si rendono conto che il collegamento a cascata di più di un livello (o in alcuni casi qualsiasi a cascata, a seconda del DBMS) comporterà operazioni a livello di record anziché operazioni impostate.
- L'eliminazione frequente e ripetuta accelera il processo di frammentazione dell'indice.
Quindi, l'eliminazione graduale è migliore, giusto? No, non proprio:
- L'impostazione di cascate diventa estremamente difficile. Quasi sempre finisci con quelle che appaiono al client come righe orfane.
- Puoi seguire solo una cancellazione. Cosa succede se la riga viene eliminata e cancellata più volte?
- Le prestazioni di lettura ne risentono, sebbene ciò possa essere in qualche modo mitigato con partizionamento, viste e / o indici filtrati.
- Come accennato in precedenza, potrebbe effettivamente essere illegale in alcuni scenari / giurisdizioni.
La verità è che entrambi questi approcci sono sbagliati. L'eliminazione è sbagliata. Se stai effettivamente facendo questa domanda, significa che stai modellando lo stato corrente anziché le transazioni. Questa è una cattiva pratica nel database-land.
Udi Dahan ne ha scritto in Don't Delete - Just Don't . C'è sempre una sorta di operazione, transazione, l'attività , o (il mio preferito termine) evento che rappresenta in realtà la "cancellazione". Va bene se in seguito si desidera denormalizzare in una tabella "stato corrente" per le prestazioni, ma farlo dopo aver inchiodato il modello transazionale, non prima.
In questo caso hai "utenti". Gli utenti sono essenzialmente clienti. I clienti hanno una relazione d'affari con te. Quella relazione non svanisce semplicemente nel nulla perché hanno cancellato il loro account. Quello che sta realmente accadendo è:
- Il cliente crea un account
- Il cliente annulla l'account
- Il cliente rinnova l'account
- Il cliente annulla l'account
- ...
In ogni caso, è lo stesso cliente e possibilmente lo stesso account (ovvero ogni rinnovo dell'account è un nuovo contratto di servizio). Quindi perché stai eliminando le righe? Questo è molto facile da modellare:
+-----------+ +-------------+ +-----------------+
| Account | --->* | Agreement | --->* | AgreementStatus |
+-----------+ +-------------+ +----------------+
| Id | | Id | | AgreementId |
| Name | | AccountId | | EffectiveDate |
| Email | | ... | | StatusCode |
+-----------+ +-------------+ +-----------------+
Questo è tutto. Questo è tutto quello che c'è da fare. Non devi mai cancellare nulla. Quanto sopra è un design abbastanza comune che offre un buon grado di flessibilità ma è possibile semplificarlo un po '; potresti decidere di non aver bisogno del livello "Accordo" e avere semplicemente "Account" in una tabella "AccountStatus".
Se una necessità frequente nella tua applicazione è quella di ottenere un elenco di accordi / account attivi , allora è una query (leggermente) complicata, ma è a questo che servono le visualizzazioni:
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 hai finito. Ora hai qualcosa con tutti i vantaggi delle soft-delete ma nessuno degli svantaggi:
- I record orfani non sono un problema perché tutti i record sono sempre visibili; basta selezionare da una vista diversa ogni volta che è necessario.
- La "cancellazione" di solito è un'operazione incredibilmente economica: basta inserire una riga in una tabella degli eventi.
- Non c'è mai alcuna possibilità di perdere alcuna storia, mai , non importa quanto male rovini.
- Puoi ancora cancellare un account se necessario (ad es. Per motivi di privacy) e sentirti a tuo agio con la consapevolezza che la cancellazione avverrà in modo pulito e non interferirà con qualsiasi altra parte dell'app / database.
L'unico problema rimasto da affrontare è il problema delle prestazioni. In molti casi in realtà risulta essere un problema a causa dell'indice cluster attivo AgreementStatus (AgreementId, EffectiveDate)
: c'è molto poco I / O che cerca di succedere lì. Ma se mai c'è un problema, ci sono modi per risolverlo, usando trigger, viste indicizzate / materializzate, eventi a livello di applicazione, ecc.
Tuttavia, non preoccuparti delle prestazioni troppo presto: è più importante che il progetto sia corretto, e "giusto" in questo caso significa utilizzare il database nel modo in cui un database deve essere utilizzato, come sistema transazionale .