Gestione degli utenti eliminati: tabella separata o uguale?


19

Lo scenario è che ho un set di utenti in espansione e con il passare del tempo, gli utenti annulleranno i loro account che attualmente contrassegniamo come "eliminati" (con un flag) nella stessa tabella.

Se gli utenti con lo stesso indirizzo email (è così che accedono gli utenti) desiderano creare un nuovo account, possono registrarsi nuovamente, ma viene creato un NUOVO account. (Abbiamo ID univoci per ogni account, quindi gli indirizzi e-mail possono essere duplicati tra quelli live e quelli eliminati).

Quello che ho notato è che in tutto il nostro sistema, nel normale corso delle cose, interroghiamo costantemente la tabella degli utenti verificando che l'utente non venga cancellato, mentre quello che sto pensando è che non è necessario farlo affatto ... ! [Chiarimento1: per "interrogare costantemente", intendevo dire che abbiamo delle domande del tipo: '... DAGLI utenti DOVE isdeleted = "0" AND ...'. Ad esempio, potrebbe essere necessario recuperare tutti gli utenti registrati per tutte le riunioni in una determinata data, quindi in QUESTA query, abbiamo anche DA utenti DOVE isdeleted = "0" - questo chiarisce il mio punto?]

(1) continue keeping deleted users in the 'main' users table
(2) keep deleted users in a separate table (mostly required for historical
    book-keeping)

Quali sono i pro e i contro di entrambi gli approcci?


Per quali motivi tieni gli utenti?
keppla,

2
Questo si chiama soft-delete. Vedi anche Eliminazione di record di database unpermenantley (soft-delete)
Sjoerd

@keppla - afferma che: "contabilità storica".
ChrisF

@ChrisF: ero interessato all'ambito: vuole tenere libri solo degli utenti, oppure ci sono ancora dei dati allegati (commenti eG, pagamenti, ecc.)
keppla,

Potrebbe aiutare a smettere di pensare a loro come cancellati (il che non è vero) e cominciare a pensare del proprio account come annullata (il che è vero).
Mike Sherrill "Cat Recall",

Risposte:


13

(1) continua a mantenere gli utenti eliminati nella tabella degli utenti "principali"

  • Pro: domande più semplici in tutti i casi
  • Contro: può degradare le prestazioni nel tempo, se c'è un alto numero di utenti

(2) mantenere gli utenti eliminati in una tabella separata (principalmente richiesta per la contabilità storica)

Ad esempio, è possibile utilizzare un trigger per spostare automaticamente gli utenti eliminati nella tabella della cronologia.

  • Pro: manutenzione più semplice per la tabella degli utenti attivi, prestazioni stabili
  • Contro: sono necessarie query diverse per la tabella della cronologia; tuttavia, poiché la maggior parte delle app non dovrebbe interessarsene, questo effetto negativo è probabilmente limitato

11
Una tabella delle partizioni (su IsDeleted) eliminerebbe i problemi di prestazioni con l'utilizzo di una singola tabella.
Ian,

1
@Ian a meno che ogni query non venga fornita con IsDeleted come criterio di query (che non sembra nella domanda originale), il partizionamento può persino causare un peggioramento delle prestazioni.
Adrian Shum,

1
@Adrian, stavo supponendo che le query più comuni sarebbero state al momento dell'accesso e che solo a nessuno degli utenti eliminati sarebbe stato permesso di accedere.
Ian

1
Utilizzare una vista indicizzata su isdeleted se diventa un problema di prestazioni e si desidera il vantaggio di una singola tabella.
JeffO,

10

Consiglio vivamente di usare la stessa tabella. Il motivo principale è l'integrità dei dati. Molto probabilmente ci saranno molte tabelle con relazioni a seconda degli utenti. Quando un utente viene eliminato, non si desidera lasciare quei record orfani.
Avere documenti orfani rende entrambi più difficili i vincoli di applicazione e rende più difficile la ricerca di informazioni storiche. L'altro comportamento da considerare se quando un utente fornisce un messaggio di posta elettronica utilizzato se si desidera che recuperi tutti i vecchi record. Funzionerebbe automaticamente usando l'eliminazione soft. Per quanto riguarda la codifica, ad esempio nella mia attuale applicazione c # linq la clausola where cancellata = 0 viene automaticamente aggiunta alla fine di tutte le query


7

"Quello che ho notato è che in tutto il nostro sistema, nel normale corso delle cose, interroghiamo costantemente la tabella degli utenti verificando che l'utente non venga cancellato"

Questo mi dà un cattivo odore di design. Dovresti nascondere un tale tipo di logica. Ad esempio, dovresti avere un UserServicemetodo isValidUser(userId)per fornire "attraverso il tuo sistema", invece di fare qualcosa del tipo:

msgstr "ottiene il record utente, controlla se l'utente è contrassegnato come eliminato".

Il modo in cui archiviare gli utenti eliminati non dovrebbe influire sulla logica aziendale.

Con un tale tipo di incapsulamento, l'argomento sopra riportato non dovrebbe più influenzare l'approccio della tua persistenza. Quindi puoi concentrarti maggiormente sui pro e contro relativi alla persistenza stessa.

Le cose da considerare includono:

  • Per quanto tempo è necessario eliminare effettivamente il record eliminato?
  • Qual è la percentuale di record eliminati?
  • Ci sarà un problema per l'integrità referenziale (ad es. L'utente viene indirizzato da un'altra tabella) se lo rimuovi effettivamente dalla tabella?
  • Stai pensando di riaprire l'utente?

Normalmente prenderei un modo combinato:

  1. Contrassegna il record come eliminato (in modo da mantenerlo per requisiti funzionali, come riaprire CA o controllare CA chiuso di recente).
  2. Dopo un periodo predefinito, spostare il record eliminato nella tabella di archiviazione (a scopo di contabilità).
  3. Eliminalo dopo un periodo di archiviazione predefinito.

1
[Chiarimento1: per "interrogare costantemente", intendevo dire che abbiamo delle domande del tipo: '... DAGLI utenti DOVE isdeleted = "0" AND ...'. Ad esempio, potrebbe essere necessario recuperare tutti gli utenti registrati per tutte le riunioni in una determinata data, quindi in QUESTA query, abbiamo anche DA utenti DOVE isdeleted = "0" - questo chiarisce il mio punto di vista?] @Adrian
Alan Beats

Sì, molto più chiaro. :) Se lo sto facendo, preferirei modificarlo come cambia lo stato dell'utente, invece di vederlo come eliminazione fisica / logica. Sebbene la quantità di codice non si riduca ("e isDeleted = '0'" vs 'e "state <>' TERMINATED '") ma tutto sembrerà molto più ragionevole, ed è normale avere anche uno stato utente diverso. È possibile eseguire anche l'eliminazione periodica degli utenti TERMINATI, come suggerito nella mia risposta precedente)
Adrian Shum,

5

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 .


1

Attualmente sto lavorando con un sistema in cui ogni tabella ha un flag Eliminato per l'eliminazione graduale. È la rovina di tutta l'esistenza. Interrompe totalmente l'integrità relazionale quando un utente può "eliminare" un record da una tabella, tuttavia i record figlio che FK tornano a quella tabella non vengono eliminati a cascata. Realizza davvero i dati del cestino dopo il passare del tempo.

Quindi, consiglio tabelle cronologiche separate.


Sicuramente senza cambiamenti cronologici a cascata, hai esattamente lo stesso problema?
glenatron,

Non nelle tabelle dei record attivi, no.
Jesse C. Slicer,

Quindi cosa succede ai record secondari che hanno eseguito l'FK dalla tabella utente dopo che l'utente è stato consegnato alla tabella cronologica?
glenatron,

Il trigger (o la logica aziendale) consegnerebbe anche i record figlio alle rispettive tabelle cronologiche. Il punto è che non puoi eliminare fisicamente il record principale (per passare alla cronologia) senza che il database ti dica che hai rotto il RI. Quindi sei costretto a progettarlo. La bandiera eliminata non forza le soft-cancellazione a cascata.
Jesse C. Slicer,

3
Dipende da cosa significhi veramente la tua cancellazione soft. Se è solo un modo per disattivarli, non è necessario regolare i record relativi a un account disattivato. Mi sembrano solo dati. E sì, devo occuparmene anche in un sistema che non ho progettato. Non significa che ti debba piacere.
JeffO,

1

Rompere il tavolo in due sarebbe la cosa più lunare che si possa immaginare.

Ecco due semplici passaggi che consiglierei:

  1. Rinominare la tabella "utenti" in "alluser".
  2. Crea una vista chiamata 'utenti' come 'seleziona * da tutti gli utenti dove cancellato = falso'.

PS Ci scusiamo per il ritardo di alcuni mesi nella risposta!


0

Se avessi recuperato gli account eliminati quando qualcuno torna con lo stesso indirizzo e-mail, avrei scelto di mantenere tutti gli utenti nella stessa tabella. Ciò renderebbe il processo di recupero dell'account banale.

Tuttavia, quando si creano nuovi account, probabilmente sarebbe più semplice spostare gli account eliminati in una tabella separata. Il sistema live non ha bisogno di queste informazioni, quindi non esporle. Come dici tu, rende le query più semplici e molto probabilmente più veloci su set di dati più grandi. Il codice più semplice è anche più facile da mantenere.


0

Non si menziona DBMS in uso. Se si dispone di Oracle con la licenza corretta, è possibile prendere in considerazione la partizione della tabella degli utenti in due partizioni: utenti attivi ed eliminati.


Quindi è necessario spostare le righe da una partizione all'altra quando si eliminano gli utenti, il che non è sicuramente il modo in cui le partizioni devono essere utilizzate.
Péter Török,

@Péter: eh? Puoi partizionare su tutti i criteri che desideri, incluso il flag eliminato.
Aaronaught,

@Aaronaught, OK, l'ho scritto male. Il DBMS può fare il lavoro per te, ma è ancora un lavoro extra (perché la riga deve essere spostata fisicamente da una posizione a un'altra, possibilmente in un file diverso), e può deteriorare la distribuzione fisica dei dati.
Péter Török,
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.