Come devono essere gestite le cancellazioni nel database?


44

Vorrei implementare una funzione di "ripristino" in un'applicazione Web in modo tale che un utente possa cambiare idea e recuperare un record eliminato. Pensi su come implementarlo? Alcune opzioni che ho preso in considerazione sono l'eliminazione del record in questione e l'archiviazione delle modifiche in una tabella di controllo separata, oppure la cancellazione del record e l'utilizzo di una colonna booleana "eliminata" per contrassegnarlo come eliminato. Quest'ultima soluzione richiederebbe una logica applicativa aggiuntiva per ignorare i record "eliminati" in circostanze normali, ma renderebbe molto più semplice implementare il ripristino dei record sul lato dell'applicazione.


Ho dimenticato di menzionare che nel secondo caso i record contrassegnati dovrebbero essere eliminati o spostati dopo un periodo di tempo trascorso ragionevole.
Abie

che database stai usando?
Evan Carroll,

La tabella temporale è la migliore soluzione per SQL Server 2016 e versioni successive.
Sameer

Risposte:


37

Sì, sceglierei sicuramente la seconda opzione, ma aggiungerei un altro campo un campo data.

Quindi aggiungi:

delete       boolean
delete_date  timestamp

Ti permetterebbe di dedicare un po 'di tempo all'azione non eliminata.

Se il tempo è inferiore a un'ora si può ripristinare.

Per cancellare davvero la voce cancellata basta creare una procedura memorizzata che pulirà ogni voce con delete impostato su true e tempo maggiore di un'ora e metterlo come una scheda cron che viene eseguita ogni 24 ore

L'ora è solo un esempio.


In alternativa, potresti avere un altro flag - cleanedo qualcosa del genere - che indica che i dati associati a questo record sono stati cancellati in modo corretto e completo. Il record può essere cancellato a meno che non cleanedsia vero, nel qual caso è irrecuperabile.
Gaurav,

14
Questo è l'approccio comune. Di solito uso un campo che deleted_atcontiene sia la semantica del valore deletebooleano che il delete_datetimestamp. Se deleted_atè NULLhandle il caso deleteè FALSEed delete_dateè NULL, deleted_atcontenente un timestamp gestisce il caso deleteè TRUEe delete_datecontiene un timestamp, risparmiando tempo, memoria e logica dell'applicazione.
Julien,

1
Mi piace il campo booleano e la data. A seconda di come si implementa la logica di eliminazione, si potrebbe anche avere una tabella distinta che contiene la data e la chiave univoca per il record che è stato "eliminato". Le procedure memorizzate lo rendono facile. Occupa lo spazio aggiuntivo per riga richiesto fino a 1 bit rispetto a 8+. Saresti anche in grado di riferire sulle eliminazioni al giorno senza toccare la tabella di origine.
AndrewSQL,

Nota: eliminare è una parola riservata in MySQL.
Jason Rikard,

Ricorda che un indice filtrato sul tuo deletedcampo può migliorare notevolmente le prestazioni quando esegui una query per le righe non cancellate
Ross Presser

21

Nelle nostre applicazioni non lo facciamo davvero eliminare nulla ad un gli utenti richiedono in ogni caso (i nostri clienti sono in ambienti regolamentati in cui l'eliminazione di qualsiasi cosa può potenzialmente portare a problemi legali).

Conserviamo le versioni precedenti in una tabella di controllo separata (quindi per la tabella some_table in cui è presente anche una tabella denominata some_table_audit) che è identica a parte avere un identificatore di versione aggiuntivo (un timestamp se il DB supporta valori di tempo sufficientemente granulari, un numero di versione intero o UUID che è una chiave esterna di una tabella di controllo generale o così via) e aggiorna automaticamente la tabella di controllo mediante trigger (quindi non è necessario che tutti i codici che aggiornano i record siano consapevoli dei requisiti di controllo).

Per di qua:

  • l'operazione di eliminazione è solo una semplice eliminazione - non è necessario aggiungere altro codice (anche se potresti voler registrare chi ha richiesto quali righe eliminare, anche se non sono effettivamente eliminate)
  • inserimenti e aggiornamenti sono altrettanto semplici
  • puoi implementare il ripristino o il ripristino semplicemente riportando la riga "normale" a una versione precedente (il trigger di controllo si attiverà di nuovo in modo che anche la tabella della traccia di controllo rifletta questa modifica)
  • puoi offrire la possibilità di rivedere o ripristinare qualsiasi versione passata non solo ripristinare l'ultima
  • non è necessario aggiungere "è contrassegnato come eliminato?" controlla ogni punto di codice che fa riferimento alla tabella in questione, o la logica "aggiorna copia di controllo" ad ogni punto di codice che elimina / aggiorna le righe (anche se è necessario decidere cosa fare con le righe eliminate nella tabella di controllo: abbiamo un cancellato / non contrassegnato per ogni versione lì quindi non c'è un buco nella cronologia se i record vengono eliminati e successivamente cancellati)
  • mantenere le copie di controllo in una tabella separata significa che è possibile dividerle facilmente in diversi filegroup.

Se si utilizza un timestamp anziché (o oltre) un numero di versione intero, è possibile utilizzarlo per eliminare le copie precedenti dopo un determinato periodo di tempo, se necessario. Ma lo spazio su disco è relativamente economico in questi giorni, quindi a meno che non abbiamo motivo di eliminare i vecchi dati (cioè le normative sulla protezione dei dati che dicono che dovresti eliminare i dati del cliente dopo X mesi / anni) non lo faremmo.


Questa risposta è di circa alcuni anni e da allora sono cambiate un paio di cose chiave che potrebbero influenzare questo tipo di pianificazione. Non entrerò nei dettagli massicci, ma frettolosamente a beneficio delle persone che leggono questo oggi:

  • SQL Server 2016 ha introdotto "tabelle temporali con versione di sistema" che fanno molto di questo lavoro per te, e altro ancora, dato che viene fornito un po 'di zucchero sintattico per rendere più semplici da costruire e mantenere query storiche e coordinano un sottoinsieme di modifiche dello schema tra tabelle di base e cronologiche. Non sono privi di avvertimenti, ma sono uno strumento potente per questo tipo di scopo. Funzionalità simili sono disponibili anche in altri sistemi DB.

  • Le modifiche alla legislazione sulla protezione dei dati, in particolare l'introduzione del GDPR, possono modificare in modo significativo la questione di quando i dati dovrebbero essere cancellati. Devi valutare la bilancia della mancata cancellazione di dati che potrebbero essere utili (o, in effetti, legalmente richiesti) a fini di revisione contabile in un secondo momento contro la necessità di rispettare i diritti delle persone (sia in generale sia come specificamente stabilito nella legislazione pertinente) quando si considera i tuoi disegni. Questo può essere un problema con le tabelle temporali con versione del sistema in quanto non è possibile modificare la cronologia per eliminare i dati personali senza modifiche a breve termine dello schema per disattivare il rilevamento della cronologia mentre si apportano modifiche.


Come gestisci la cancellazione e la ridenominazione delle colonne? Impostare tutto su nullable?
Stijn,

1
@Stijn: Non capita spesso che le strutture vengano cambiate, quindi non si presenta molto. In genere i colunms non vengono mai rimossi una volta che sono esistiti nella produzione - se smettono di essere usati, lascia cadere tutti i vincoli che li fermerebbero NULL (o aggiungi valori predefiniti per gestire i vincoli usando un "valore magico", anche se sembra più sporco) e smetti di riferirti a loro in altri codici. Per rinominare: aggiungi nuovo, smetti di usare vecchio e copia i dati dal vecchio al nuovo, se necessario. Se si rinomina le colonne, accertarsi di apportare la stessa modifica alle tabelle di base e di controllo contemporaneamente.
David Spillett,

9

Con una colonna booleana eliminata, inizierai ad avere problemi se la tua tabella inizia a crescere e diventa davvero grande. Ti suggerisco di spostare le colonne eliminate una volta alla settimana (più o meno a seconda delle tue specifiche) in una tabella diversa. In questo modo hai una bella tabella attiva e una grande contenente tutti i record raccolti nel tempo.


7

Andrei con il tavolo separato. Ruby on Rails ha un acts_as_versionedplugin, che sostanzialmente salva una riga in un'altra tabella con il postfix _versionprima di aggiornarlo. Sebbene non sia necessario quel comportamento esatto, dovrebbe funzionare anche per il tuo caso (copia prima dell'eliminazione).

Come @Spredzy, consiglierei anche di aggiungere una delete_datecolonna per poter eliminare programmaticamente i record che non sono stati ripristinati dopo X ore / giorni / qualunque cosa.


4

La soluzione che utilizziamo internamente per questa materia è quella di avere una colonna di stato con alcuni valori codificati per alcuni stati specifici dell'oggetto: Eliminato, Attivo, Inattivo, Aperto, Chiuso, Bloccato - ogni stato con un certo significato utilizzato nell'applicazione. Dal punto di vista db non rimuoviamo gli oggetti, cambiamo semplicemente lo stato e conserviamo la cronologia per ogni modifica nella tabella degli oggetti.


3

Quando dici che "L'ultima soluzione richiederebbe una logica applicativa aggiuntiva per ignorare i record" cancellati ", la soluzione semplice è avere una vista che li filtra.


Non è solo una questione di vista. Qualsiasi operazione eseguita sul set dovrebbe escludere i record "eliminati".
Abie,

2

Simile a quanto suggerito da Spredzy, utilizziamo un campo timestamp per la cancellazione in tutte le nostre applicazioni. Il valore booleano è superfluo, poiché il timestamp impostato indica che il record è stato eliminato. In questo modo, il nostro DOP si aggiunge sempre AND (deleted IS NULL OR deleted = 0)alle istruzioni selezionate, a meno che il modello non richieda esplicitamente di includere i record eliminati.

Al momento non raccogliamo immondizia su alcuna tabella tranne che contenga BLOB o testi; lo spazio è banale se i record sono ben normalizzati e l'indicizzazione del deletedcampo ha un impatto limitato sulla velocità selezionata.


0

In alternativa, potresti mettere l'onere sugli utenti (e sugli sviluppatori) e seguire una sequenza di "Sei sicuro?", "Ne sei sicuro?" e "Sei assolutamente, bene e veramente sicuro?" domande prima che il record venga eliminato. Leggermente faceto ma vale la pena considerare.


0

Sono abituato a vedere righe di tabelle con colonne come "DeletedDate" e non mi piacciono. L'idea stessa di "cancellato" è che la voce non avrebbe dovuto essere fatta in primo luogo. In pratica, non possono essere rimossi dal database ma non li voglio con i miei dati caldi. Le righe eliminate logicamente sono, per definizione, dati freddi a meno che qualcuno non voglia vedere specificamente i dati eliminati.

Inoltre, ogni query scritta deve escluderli in modo specifico e anche gli indici devono considerarli.

Quello che vorrei vedere è una modifica a livello di architettura del database e a livello di applicazione: creare uno schema chiamato 'eliminato'. Ogni tabella definita dall'utente ha un identico equivalente nello schema "eliminato" con un campo aggiuntivo contenente metadati: l'utente che lo ha eliminato e quando. È necessario creare chiavi esterne.

Successivamente, le eliminazioni diventano inserzioni-eliminazioni. Innanzitutto la riga da eliminare viene inserita nella sua controparte dello schema "eliminato". La riga in questione nella tabella principale può quindi essere eliminata. Tuttavia, è necessario aggiungere una logica aggiuntiva da qualche parte lungo la linea. Le violazioni delle chiavi esterne possono essere gestite.

Le chiavi esterne devono essere gestite correttamente. È buona norma avere una riga eliminata logicamente ma il cui principale / unico ha colonne in altre tabelle che si riferiscono ad essa. Questo non dovrebbe succedere comunque. Un lavoro regolare può rimuovere le righe della vedova (righe le cui chiavi primarie non hanno riferimenti in altre tabelle nonostante la presenza di una chiave esterna. Questa è, tuttavia, una logica aziendale.

Il vantaggio complessivo è la riduzione dei metadati nella tabella e il miglioramento delle prestazioni che comporta. La colonna 'deleteDate' dice che questa riga non dovrebbe essere effettivamente qui ma, per comodità, la lasciamo lì e lasciamo che la query SQL la gestisca. Se una copia della riga eliminata viene mantenuta in uno schema "eliminato", la tabella principale con i dati attivi presenta una percentuale più elevata di dati attivi (supponendo che sia archiviata in modo tempestivo) e meno colonne di metadati non necessarie. Gli indici e le query non devono più considerare questo campo. Minore è la dimensione delle righe, più righe possono essere adattate su una pagina, più velocemente SQL Server può funzionare.

Lo svantaggio principale è la dimensione dell'operazione. Ora ci sono due operazioni anziché una, oltre alla logica aggiuntiva e alla gestione degli errori. Può portare a un blocco maggiore rispetto all'aggiornamento di una singola colonna che altrimenti richiederebbe. La transazione mantiene i blocchi sulla tabella più a lungo e sono presenti due tabelle. L'eliminazione dei dati di produzione, almeno secondo la mia esperienza, è qualcosa che viene fatto raramente. Tuttavia, in una delle tabelle principali il 7,5% di quasi 100 milioni di voci ha una voce nella colonna "Data eliminata".

Come risposta alla domanda, l'applicazione dovrebbe essere a conoscenza di "undelete's". Dovrebbe semplicemente fare la stessa cosa in ordine inverso: inserire la riga dallo schema "eliminato" nella tabella principale e quindi eliminare la riga dallo "schema eliminato". Ancora una volta sono necessarie ulteriori logiche e gestione degli errori per evitare errori, problemi con chiavi esterne e simili.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.