Umgang mit gelöschten Benutzern - separate oder gleiche Tabelle?

19

Das Szenario sieht so aus, dass immer mehr Benutzer angemeldet sind. Mit der Zeit kündigen Benutzer ihre Konten, die wir derzeit in derselben Tabelle als "gelöscht" (mit einem Flag) markieren.

Wenn Benutzer mit derselben E-Mail-Adresse (so melden sich Benutzer an) ein neues Konto erstellen möchten, können sie sich erneut anmelden, es wird jedoch ein NEUES Konto erstellt. (Wir haben eindeutige IDs für jedes Konto, sodass E-Mail-Adressen zwischen aktiven und gelöschten Adressen dupliziert werden können.)

Was mir aufgefallen ist, ist, dass wir im gesamten System ständig die Benutzertabelle abfragen, um sicherzustellen, dass der Benutzer nicht gelöscht wird. Ich denke jedoch, dass wir das überhaupt nicht tun müssen ... ! [Klarstellung1: Mit "ständigem Abfragen" meine ich, dass wir Fragen haben, die wie folgt lauten: "... FROM users WHERE isdeleted =" 0 "AND ...". Beispielsweise müssen wir möglicherweise alle Benutzer abrufen, die für alle Besprechungen an einem bestimmten Datum registriert sind. In DIESER Abfrage haben wir also auch FROM-Benutzer, bei denen isdeleted = "0" ist. Macht dies meinen Standpunkt klarer?]

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

Was sind die Vor- und Nachteile beider Ansätze?

Alan Beats
quelle
Aus welchen Gründen behalten Sie die Nutzer?
Keppla
2
Dies wird als Soft-Delete bezeichnet. Siehe auch Löschen von Datenbankeinträgen unpermenantley (soft-delete)
Sjoerd
@keppla - er erwähnt das: "historische Buchhaltung".
ChrisF
@ ChrisF: Ich interessierte mich für den Umfang: Will er nur die Bücher der Benutzer führen, oder sind noch Daten beigefügt (z.
B.
Es könnte helfen , von ihnen als gelöscht aufhören zu denken (was nicht stimmt) und das Denken ihres Kontos beginnt als annulliert (das ist wahr).
Mike Sherrill 'Cat Recall'

Antworten:

13

(1) Bewahren Sie gelöschte Benutzer weiterhin in der Hauptbenutzertabelle auf

  • Vorteile: einfachere Abfragen in allen Fällen
  • Nachteile: Kann die Leistung im Laufe der Zeit beeinträchtigen, wenn eine große Anzahl von Benutzern vorhanden ist

(2) Gelöschte Benutzer in einer separaten Tabelle aufbewahren (meistens für die historische Buchhaltung erforderlich)

Sie können z. B. einen Trigger verwenden, um gelöschte Benutzer automatisch in die Verlaufstabelle zu verschieben.

  • Vorteile: einfachere Wartung für die Tabelle der aktiven Benutzer, stabile Leistung
  • Nachteile: Sie benötigen unterschiedliche Abfragen für die Verlaufstabelle. Da sich die meisten Apps jedoch nicht dafür interessieren sollen, ist dieser negative Effekt wahrscheinlich begrenzt
Péter Török
quelle
11
Eine Partitionstabelle (auf IsDeleted) würde die Leistungsprobleme bei der Verwendung einer einzelnen Tabelle beseitigen.
Ian
1
@Ian Wenn nicht jede Abfrage mit IsDeleted als Abfragekriterium versehen ist (was in der ursprünglichen Frage nicht vorkommt), kann die Partitionierung sogar zu Leistungseinbußen führen.
Adrian Shum
1
@Adrian, ich ging davon aus, dass die häufigsten Anfragen zum Zeitpunkt der Anmeldung gestellt werden und sich nur keine gelöschten Benutzer anmelden dürfen.
Ian
1
Verwenden Sie eine indizierte Ansicht für isdeleted, wenn dies zu einem Leistungsproblem wird und Sie die Vorteile einer einzelnen Tabelle nutzen möchten.
JeffO
10

Ich empfehle dringend, die gleiche Tabelle zu verwenden. Der Hauptgrund ist die Datenintegrität. Höchstwahrscheinlich wird es viele Tabellen mit Beziehungen geben, abhängig von den Benutzern. Wenn ein Benutzer gelöscht wird, möchten Sie diese Datensätze nicht verwaist lassen.
Verwaiste Aufzeichnungen zu haben, erschwert die Durchsetzung von Einschränkungen und erschwert das Nachschlagen historischer Informationen. Das andere zu berücksichtigende Verhalten, wenn ein Benutzer eine verwendete E-Mail bereitstellt, um alle alten Datensätze wiederherzustellen. Dies würde automatisch mit Soft Delete funktionieren. Was die Codierung betrifft, wird beispielsweise in meiner aktuellen c # linq-Anwendung die where deleted = 0-Klausel automatisch an das Ende aller Abfragen angehängt

Andrej
quelle
7

"Was mir aufgefallen ist, ist, dass wir systemweit ständig die Benutzertabelle abfragen, um zu überprüfen, ob der Benutzer nicht gelöscht wurde."

Dies gibt mir einen schlechten Geruch von Design. Sie sollten eine solche Logik verbergen. Beispielsweise sollten Sie UserServiceeine Methode isValidUser(userId)für die Verwendung "auf Ihrem gesamten System" bereitstellen, anstatt wie folgt vorzugehen:

msgstr "Benutzerdatensatz abrufen, prüfen, ob Benutzer als gelöscht markiert ist".

Ihre Art, gelöschte Benutzer zu speichern, sollte sich nicht auf die Geschäftslogik auswirken.

Mit einer solchen Art der Verkapselung sollte das obige Argument den Ansatz Ihrer Persistenz nicht mehr beeinflussen. Dann können Sie sich mehr auf die Vor- und Nachteile der Persistenz konzentrieren.

Dinge zu beachten sind:

  • Wie lange soll der gelöschte Datensatz tatsächlich gelöscht werden?
  • Wie hoch ist der Anteil gelöschter Datensätze?
  • Wird es ein Problem mit der referenziellen Integrität geben (z. B. wird der Benutzer von einer anderen Tabelle verwiesen), wenn Sie ihn tatsächlich aus der Tabelle entfernen?
  • Ziehen Sie in Betracht, den Benutzer erneut zu öffnen?

Normalerweise würde ich einen kombinierten Weg gehen:

  1. Kennzeichnen Sie den Datensatz als gelöscht (um ihn für funktionale Anforderungen wie das erneute Öffnen von ac oder das Überprüfen kürzlich geschlossener ac aufzubewahren).
  2. Verschieben Sie den gelöschten Datensatz nach einem vordefinierten Zeitraum in die Archivtabelle (zu Buchhaltungszwecken).
  3. Bereinigen Sie es nach einer vordefinierten Archivierungsperiode.
Adrian Shum
quelle
1
[Klarstellung1: Mit "ständigem Abfragen" meine ich, dass wir Fragen haben, die wie folgt lauten: "... FROM users WHERE isdeleted =" 0 "AND ...". Zum Beispiel müssen wir alle Benutzer für alle Sitzungen zu einem bestimmten Zeitpunkt registriert holen, so in dieser Abfrage, wir auch haben FROM users WHERE isDeleted = „0“ - bedeutet dies meinen Punkt klarer zu machen] @Adrian
Alan Beats
Ja, viel klarer. :) Wenn ich das tue, würde ich es eher als Statusänderung des Benutzers machen, anstatt es als physisches / logisches Löschen zu betrachten. Die Menge an Code wird sich zwar nicht verringern ("and isDeleted = '0'" vs 'und "state <>' TERMINATED '"), aber alles wird viel sinnvoller aussehen, und es ist normal, auch einen anderen Benutzerstatus zu haben. Die regelmäßige Bereinigung von TERMINATED-Benutzern kann ebenfalls durchgeführt werden (wie in meiner vorherigen Antwort vorgeschlagen)
Adrian Shum,
5

Um diese Frage richtig zu beantworten, müssen Sie sich zunächst entscheiden: Was bedeutet "Löschen" im Kontext dieses Systems / dieser Anwendung?

Um diese Frage zu beantworten , müssen Sie noch eine weitere Frage beantworten: Warum werden Datensätze gelöscht?

Es gibt eine Reihe von guten Gründen, warum ein Benutzer Daten löschen muss. Normalerweise finde ich, dass es genau einen Grund (pro Tabelle) gibt, warum ein Löschen notwendig sein könnte. Einige Beispiele sind:

  • So fordern Sie Speicherplatz zurück
  • Harte Löschung gemäß Aufbewahrungs- / Datenschutzrichtlinie erforderlich;
  • Beschädigte / hoffnungslos falsche Daten, einfacher zu löschen und neu zu generieren als zu reparieren.
  • Die meisten Zeilen werden gelöscht, z. B. eine Protokolltabelle, die auf X Datensätze / Tage begrenzt ist.

Es gibt auch einige sehr schlechte Gründe für ein hartes Löschen (dazu später mehr):

  • So korrigieren Sie einen geringfügigen Fehler. Dies unterstreicht normalerweise die Faulheit der Entwickler und eine feindliche Benutzeroberfläche.
  • Eine Transaktion "stornieren" (zB Rechnung, die niemals hätte fakturiert werden dürfen).
  • Weil du kannst .

Warum, fragen Sie sich, ist das wirklich so eine große Sache? Was ist los mit guten alten DELETE?

  • In jedem System, das auch nur remote an Geld gebunden ist, verstößt das Löschen gegen alle möglichen Buchhaltungserwartungen, selbst wenn es in ein Archiv / eine Tombstone-Tabelle verschoben wird. Die richtige Vorgehensweise ist ein rückwirkendes Ereignis .
  • Archivtabellen neigen dazu, vom Live-Schema abzuweichen. Wenn Sie nur eine neu hinzugefügte Spalte oder Kaskade vergessen, haben Sie diese Daten dauerhaft verloren.
  • Ein hartes Löschen kann eine sehr teure Operation sein, insbesondere bei Kaskaden . Viele Leute wissen nicht, dass das Kaskadieren von mehr als einer Ebene (oder in einigen Fällen , je nach DBMS, jedes Kaskadieren) Operationen auf Rekordebene anstelle von festgelegten Operationen zur Folge hat.
  • Wiederholtes, häufiges Löschen beschleunigt den Prozess der Indexfragmentierung.

Soft Delete ist also besser, oder? Nein nicht wirklich:

  • Das Einrichten von Kaskaden wird extrem schwierig. Sie haben fast immer das, was dem Kunden als verwaiste Zeilen erscheint.
  • Sie können nur eine Löschung verfolgen . Was passiert, wenn die Zeile mehrmals gelöscht und wiederhergestellt wird?
  • Die Leseleistung leidet, obwohl dies durch Partitionierung, Ansichten und / oder gefilterte Indizes etwas gemindert werden kann.
  • Wie bereits angedeutet, kann es in einigen Szenarien / Gerichtsbarkeiten tatsächlich illegal sein.

Die Wahrheit ist, dass beide Ansätze falsch sind. Löschen ist falsch. Wenn Sie diese Frage tatsächlich stellen, bedeutet dies, dass Sie anstelle der Transaktionen den aktuellen Status modellieren. Dies ist eine schlechte, schlechte Praxis im Datenbankland.

Udi Dahan schrieb darüber in Don't Delete - Just Don't . Es gibt immer irgendeine Art von Aufgabe, Transaktion, Aktivität oder (mein bevorzugter Begriff) Ereignis, das tatsächlich das "Löschen" darstellt. Es ist in Ordnung, wenn Sie anschließend eine Denormalisierung in eine Tabelle mit dem aktuellen Status durchführen möchten, dies jedoch erst, nachdem Sie das Transaktionsmodell festgelegt haben.

In diesem Fall haben Sie "Benutzer". Benutzer sind im Wesentlichen Kunden. Kunden haben eine Geschäftsbeziehung mit Ihnen. Diese Beziehung verschwindet nicht einfach in Luft, weil sie ihren Account gekündigt hat. Was wirklich passiert ist:

  • Kunde legt Konto an
  • Der Kunde storniert das Konto
  • Kunde erneuert Konto
  • Der Kunde storniert das Konto
  • ...

In jedem Fall handelt es sich um denselben Kunden und möglicherweise um denselben Account (dh bei jeder Account-Verlängerung handelt es sich um einen neuen Servicevertrag). Warum löschen Sie Zeilen? Dies ist sehr einfach zu modellieren:

+-----------+       +-------------+       +-----------------+
| Account   | --->* | Agreement   | --->* | AgreementStatus |
+-----------+       +-------------+       +----------------+
| Id        |       | Id          |       | AgreementId     |
| Name      |       | AccountId   |       | EffectiveDate   |
| Email     |       | ...         |       | StatusCode      |
+-----------+       +-------------+       +-----------------+

Das ist es. Das ist alles dazu. Sie müssen nie etwas löschen. Das oben Genannte ist ein recht gebräuchliches Design, das ein gutes Maß an Flexibilität bietet, das Sie jedoch ein wenig vereinfachen können. Sie könnten entscheiden, dass Sie die Stufe "Vereinbarung" nicht benötigen und "Konto" einfach zu einer "AccountStatus" -Tabelle wechseln lassen.

Wenn in Ihrer Anwendung häufig eine Liste der aktiven Vereinbarungen / Konten benötigt wird, ist dies eine (geringfügig) knifflige Abfrage, für die die folgenden Ansichten jedoch vorgesehen sind:

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
)

Und du bist fertig. Jetzt haben Sie etwas mit allen Vorteilen von Soft-Deletes, aber keinen der Nachteile:

  • Verwaiste Datensätze sind kein Problem, da alle Datensätze jederzeit sichtbar sind. Sie können bei Bedarf einfach aus einer anderen Ansicht auswählen.
  • "Löschen" ist normalerweise ein unglaublich billiger Vorgang - nur eine Zeile in eine Ereignistabelle einfügen.
  • Es gibt nie eine Chance , jede Geschichte zu verlieren, immer , egal wie schlecht Sie vermasseln.
  • Sie können ein Konto nach wie vor hart löschen, wenn Sie dies benötigen (z. B. aus Datenschutzgründen), und Sie können sich darauf verlassen, dass die Löschung sauber vonstatten geht und keinen anderen Teil der App / Datenbank beeinträchtigt.

Das einzige noch zu lösende Problem ist das Leistungsproblem. In vielen Fällen stellt sich heraus, dass es aufgrund des Clustered-Index kein Problem darstellt AgreementStatus (AgreementId, EffectiveDate)- dort wird nur sehr wenig nach E / A gesucht. Sollte es dennoch zu Problemen kommen, gibt es Möglichkeiten, diese zu lösen, indem Trigger, indizierte / materialisierte Ansichten, Ereignisse auf Anwendungsebene usw. verwendet werden.

Sorgen Sie sich jedoch nicht zu früh um die Leistung - es ist wichtiger, das richtige Design zu finden. "Richtig" bedeutet in diesem Fall, die Datenbank so zu verwenden, wie sie als Transaktionssystem verwendet werden soll.

Aaronaught
quelle
1

Ich arbeite derzeit mit einem System, in dem jede Tabelle ein gelöschtes Flag für Soft-Delete hat. Es ist der Fluch aller Existenz. Es bricht die relationale Integrität vollständig, wenn ein Benutzer einen Datensatz aus einer Tabelle "löschen" kann. Untergeordnete Datensätze, deren FK zurück zu dieser Tabelle liegt, werden jedoch nicht automatisch gelöscht. Sorgt nach Ablauf der Zeit für Mülldaten.

Daher empfehle ich separate Verlaufstabellen.

Jesse C. Slicer
quelle
Sicherlich ohne kaskadierte Geschichtsverschiebungen, haben Sie genau das gleiche Problem?
Glenatron
Nicht in Ihren aktiven Aufnahmetabellen, nein.
Jesse C. Slicer
Was passiert nun mit untergeordneten Datensätzen, die vom Benutzertisch entfernt wurden, nachdem der Benutzer in die Verlaufstabelle aufgenommen wurde?
Glenatron
Ihr Auslöser (oder Ihre Geschäftslogik) würde die untergeordneten Datensätze auch ihren jeweiligen Verlaufstabellen zuordnen. Der Punkt ist, dass Sie den übergeordneten Datensatz nicht physisch löschen können (um in den Verlauf zu wechseln), ohne dass die Datenbank Ihnen mitteilt, dass Sie RI beschädigt haben. Sie müssen es also entwerfen. Mit dem gelöschten Flag werden keine kaskadierenden Soft-Deletes erzwungen.
Jesse C. Slicer
3
Kommt darauf an, was Ihr Soft Delete wirklich bedeutet. Wenn es nur eine Möglichkeit ist, sie zu deaktivieren, müssen die Datensätze für ein deaktiviertes Konto nicht angepasst werden. Scheint mir nur Daten zu sein. Und ja, ich muss mich auch mit einem System auseinandersetzen, das ich nicht entworfen habe. Das heißt nicht, dass du es mögen musst.
JeffO
1

Den Tisch in zwei Teile zu teilen, wäre das Schlimmste, was man sich vorstellen kann.

Hier sind die zwei sehr einfachen Schritte, die ich empfehlen würde:

  1. Benennen Sie die Tabelle 'users' in 'allusers' um.
  2. Erstellen Sie eine Ansicht mit dem Namen "Benutzer" als "Auswahl * von Benutzern, bei denen gelöscht = falsch".

PS Entschuldigen Sie die monatelange Verzögerung bei der Beantwortung!

Mike Nakis
quelle
0

Wenn Sie gelöschte Konten wiederhergestellt hätten, wenn jemand mit derselben E-Mail-Adresse zurückgekehrt wäre, hätte ich alle Benutzer in derselben Tabelle belassen. Dies würde den Kontowiederherstellungsprozess trivial machen.

Wenn Sie jedoch neue Konten erstellen, ist es wahrscheinlich einfacher, gelöschte Konten in eine separate Tabelle zu verschieben. Das Live-System benötigt diese Informationen nicht, machen Sie sie also nicht zugänglich. Wie Sie sagen, werden die Abfragen bei größeren Datenmengen einfacher und möglicherweise auch schneller. Einfacherer Code ist auch einfacher zu pflegen.

ChrisF
quelle
0

Sie erwähnen das verwendete DBMS nicht. Wenn Sie über eine Oracle-Lizenz verfügen, können Sie die Benutzertabelle in zwei Partitionen unterteilen: aktive und gelöschte Benutzer.

mczajk
quelle
Dann müssen Sie beim Löschen von Benutzern Zeilen von einer Partition auf eine andere verschieben. Dies ist definitiv nicht die Art und Weise, wie Partitionen verwendet werden sollen.
Péter Török
@ Péter: Huh? Sie können nach beliebigen Kriterien partitionieren, einschließlich des gelöschten Flags.
Aaronaught
@Aaronaught, OK, ich habe es falsch formuliert. Das DBMS kann die Arbeit für Sie erledigen, es ist jedoch immer noch zusätzliche Arbeit (da die Zeile physisch von einem Ort an einen anderen verschoben werden muss, möglicherweise in eine andere Datei), und es kann die physische Verteilung von Daten beeinträchtigen.
Péter Török