Versionskontrolle eines Datensatzes in einer Datenbank


176

Angenommen, ich habe einen Datensatz in der Datenbank und sowohl Administrator- als auch normale Benutzer können Aktualisierungen vornehmen.

Kann jemand einen guten Ansatz / eine gute Architektur vorschlagen, wie jede Änderung in dieser Tabelle versioniert werden kann, damit ein Datensatz auf eine frühere Revision zurückgesetzt werden kann?

Antworten:


164

Angenommen, Sie haben eine FOOTabelle, die Administratoren und Benutzer aktualisieren können. Meistens können Sie Abfragen für die FOO-Tabelle schreiben. Glückliche Tage.

Dann würde ich eine FOO_HISTORYTabelle erstellen . Dies hat alle Spalten der FOOTabelle. Der Primärschlüssel entspricht FOO plus einer RevisionNumber-Spalte. Es gibt einen Fremdschlüssel von FOO_HISTORYbis FOO. Sie können auch Spalten hinzufügen, die sich auf die Revision beziehen, z. B. UserId und RevisionDate. Füllen Sie die Revisionsnummern in immer größerer Weise über alle *_HISTORYTabellen hinweg (dh aus einer Oracle-Sequenz oder einer gleichwertigen Sequenz). Verlassen Sie sich nicht darauf, dass es nur eine Änderung in einer Sekunde gibt (dh nicht RevisionDatein den Primärschlüssel eingeben).

Jetzt FOOfügen Sie bei jedem Update kurz vor dem Update die alten Werte ein FOO_HISTORY. Sie tun dies auf einer grundlegenden Ebene in Ihrem Design, damit Programmierer diesen Schritt nicht versehentlich verpassen können.

Wenn Sie eine Zeile löschen möchten, FOOhaben Sie einige Möglichkeiten. Entweder kaskadieren und löschen Sie den gesamten Verlauf oder führen Sie ein logisches Löschen durch, indem Sie FOOals gelöscht markieren.

Diese Lösung ist gut, wenn Sie sich hauptsächlich für die aktuellen Werte und nur gelegentlich für die Geschichte interessieren. Wenn Sie den Verlauf immer benötigen, können Sie effektive Start- und Enddaten festlegen und alle Aufzeichnungen in FOOsich behalten . Jede Abfrage muss dann diese Daten überprüfen.


1
Sie können die Überwachungstabelle mit Datenbank-Triggern aktualisieren, wenn Ihre Datenzugriffsschicht dies nicht direkt unterstützt. Es ist auch nicht schwer, einen Codegenerator zu erstellen, um die Trigger zu erstellen, die Introspektion aus dem Systemdatenwörterbuch verwenden.
ConcernedOfTunbridgeWells

44
Ich würde empfehlen, dass Sie tatsächlich die neuen Daten einfügen , nicht die vorherigen, damit die Verlaufstabelle alle Daten enthält. Obwohl redundante Daten gespeichert werden, werden die Sonderfälle beseitigt, die für die Suche in beiden Tabellen erforderlich sind, wenn historische Daten erforderlich sind.
Nerdfest

6
Persönlich würde ich empfehlen, nichts zu löschen (dies auf eine bestimmte Reinigungsaktivität zu verschieben) und eine Spalte "Aktionstyp" zu haben, um anzugeben, ob es sich um Einfügen / Aktualisieren / Löschen handelt. Beim Löschen kopieren Sie die Zeile wie gewohnt, setzen jedoch "Löschen" in die Spalte "Aktionstyp".
Neil Barnwell

3
@Hydrargyrum Eine Tabelle mit den aktuellen Werten bietet eine bessere Leistung als eine Ansicht der historischen Tabelle. Möglicherweise möchten Sie auch Fremdschlüssel definieren, die auf die aktuellen Werte verweisen.
WW.

2
There is a foreign key from FOO_HISTORY to FOO': schlechte Idee, ich möchte Datensätze aus foo löschen, ohne den Verlauf zu ändern. Die Verlaufstabelle sollte bei normaler Verwendung nur eingefügt werden.
Jasen

46

Ich denke, Sie möchten den Inhalt von Datenbankeinträgen versionieren (wie es StackOverflow tut, wenn jemand eine Frage / Antwort bearbeitet). Ein guter Ausgangspunkt könnte ein Datenbankmodell sein, das Revisionsverfolgung verwendet .

Das beste Beispiel, das mir in den Sinn kommt, ist MediaWiki, die Wikipedia-Engine. Vergleichen Sie die Datenbank - Diagramm hier , insbesondere die Revisionstabelle .

Je nachdem, welche Technologien Sie verwenden, müssen Sie einige gute Diff / Merge-Algorithmen finden.

Überprüfen Sie diese Frage, wenn es sich um .NET handelt.


30

In der BI-Welt können Sie dies erreichen, indem Sie der Tabelle, die Sie versionieren möchten, ein startDate und ein endDate hinzufügen. Wenn Sie den ersten Datensatz in die Tabelle einfügen, wird das Startdatum ausgefüllt, das Enddatum jedoch null. Wenn Sie den zweiten Datensatz einfügen, aktualisieren Sie auch das Enddatum des ersten Datensatzes mit dem Startdatum des zweiten Datensatzes.

Wenn Sie den aktuellen Datensatz anzeigen möchten, wählen Sie den Datensatz aus, bei dem endDate null ist.

Dies wird manchmal als sich langsam ändernde Dimension vom Typ 2 bezeichnet . Siehe auch TupleVersioning


Wird mein Tisch mit diesem Ansatz nicht ziemlich groß?
Niels Bosma

1
Ja, aber Sie können damit umgehen, indem Sie die Tabelle indizieren und / oder partitionieren. Außerdem wird es nur eine kleine Handvoll großer Tische geben. Die meisten werden viel kleiner sein.
ConcernedOfTunbridgeWells

Wenn ich mich nicht irre, ist der einzige Nachteil hier, dass es Änderungen auf einmal pro Sekunde begrenzt, richtig?
Pimbrouwers

@pimbrouwers ja, letztendlich hängt es von der Genauigkeit der Felder und der Funktion ab, mit der sie gefüllt werden.
Dave Neeley

9

Upgrade auf SQL 2008.

Versuchen Sie, SQL Change Tracking in SQL 2008 zu verwenden. Anstelle von Zeitstempeln und Tombstone-Spalten-Hacks können Sie diese neue Funktion zum Verfolgen von Änderungen an Daten in Ihrer Datenbank verwenden.

MSDN SQL 2008 Change Tracking


7

Ich wollte nur hinzufügen, dass eine gute Lösung für dieses Problem die Verwendung einer temporären Datenbank ist . Viele Datenbankanbieter bieten diese Funktion entweder sofort oder über eine Erweiterung an. Ich habe die temporale Tabellenerweiterung erfolgreich mit PostgreSQL verwendet, aber andere haben sie auch. Wenn Sie einen Datensatz in der Datenbank aktualisieren, behält die Datenbank auch die vorherige Version dieses Datensatzes bei.


6

Zwei Optionen:

  1. Haben Sie eine Verlaufstabelle - fügen Sie die alten Daten in diese Verlaufstabelle ein, wenn das Original aktualisiert wird.
  2. Audit-Tabelle - Speichern Sie die Vorher- und Nachher-Werte - nur für die geänderten Spalten in einer Audit-Tabelle zusammen mit anderen Informationen, z. B. wer wann aktualisiert hat.

5

Sie können die Überwachung einer SQL-Tabelle über SQL-Trigger durchführen. Über einen Trigger können Sie auf 2 spezielle Tabellen zugreifen ( eingefügt und gelöscht ). Diese Tabellen enthalten die genauen Zeilen, die bei jeder Aktualisierung der Tabelle eingefügt oder gelöscht wurden. In der Trigger-SQL können Sie diese geänderten Zeilen in die Prüftabelle einfügen. Dieser Ansatz bedeutet, dass Ihre Prüfung für den Programmierer transparent ist. keine Anstrengung von ihnen oder Implementierungskenntnisse erfordern.

Der zusätzliche Vorteil dieses Ansatzes besteht darin, dass die Überwachung unabhängig davon erfolgt, ob der SQL-Vorgang über Ihre Datenzugriffs-DLLs oder über eine manuelle SQL-Abfrage erfolgt ist. (da die Überwachung auf dem Server selbst durchgeführt wird).


3

Sie sagen nicht, welche Datenbank, und ich sehe es nicht in den Post-Tags. Wenn es sich um Oracle handelt, kann ich den in Designer integrierten Ansatz empfehlen: Verwenden Sie Journaltabellen . Wenn es sich um eine andere Datenbank handelt, empfehle ich grundsätzlich auch den gleichen Weg ...

Die Art und Weise, wie es funktioniert, falls Sie es in einer anderen Datenbank replizieren möchten oder wenn Sie es nur verstehen möchten, ist, dass für eine Tabelle auch eine Schattentabelle erstellt wird, nur eine normale Datenbanktabelle mit denselben Feldspezifikationen , plus einige zusätzliche Felder: wie die zuletzt ausgeführte Aktion (Zeichenfolge, typische Werte "INS" zum Einfügen, "UPD" zum Aktualisieren und "DEL" zum Löschen), Datum / Uhrzeit für den Zeitpunkt der Aktion und Benutzer-ID für wen es.

Durch Trigger fügt jede Aktion in eine Zeile in der Tabelle eine neue Zeile in die Journaltabelle mit den neuen Werten ein, welche Aktion wann und von welchem ​​Benutzer ausgeführt wurde. Sie löschen niemals Zeilen (zumindest nicht in den letzten Monaten). Ja, es wird groß, leicht Millionen von Zeilen, aber Sie können den Wert für jeden Datensatz zu jedem Zeitpunkt nachverfolgen, seit das Journaling gestartet wurde oder die alten Journalzeilen zuletzt gelöscht wurden und wer die letzte Änderung vorgenommen hat.

In Oracle wird alles, was Sie benötigen, automatisch als SQL-Code generiert. Sie müssen ihn lediglich kompilieren / ausführen. und es kommt mit einer grundlegenden CRUD-Anwendung (eigentlich nur "R"), um es zu überprüfen.


2

Ich mache auch das Gleiche. Ich mache eine Datenbank für Unterrichtspläne. Diese Pläne erfordern Flexibilität bei der Versionierung atomarer Änderungen. Mit anderen Worten, jede noch so kleine Änderung der Unterrichtspläne muss zulässig sein, aber auch die alte Version muss intakt bleiben. Auf diese Weise können Unterrichtsersteller Unterrichtspläne bearbeiten, während die Schüler sie verwenden.

Die Art und Weise, wie es funktionieren würde, ist, dass sobald ein Schüler eine Lektion absolviert hat, seine Ergebnisse an die Version angehängt werden, die er abgeschlossen hat. Wenn eine Änderung vorgenommen wird, verweisen die Ergebnisse immer auf ihre Version.

Auf diese Weise ändern sich die Ergebnisse eines Unterrichtskriteriums nicht, wenn es gelöscht oder verschoben wird.

Derzeit mache ich das so, indem ich alle Daten in einer Tabelle verarbeite. Normalerweise hätte ich nur ein ID-Feld, aber bei diesem System verwende ich eine ID und eine Sub-ID. Die sub_id bleibt durch Aktualisierungen und Löschungen immer in der Zeile. Die ID wird automatisch inkrementiert. Die Unterrichtsplansoftware wird mit der neuesten sub_id verknüpft. Die Schülerergebnisse werden mit der ID verknüpft. Ich habe auch einen Zeitstempel eingefügt, um zu verfolgen, wann Änderungen vorgenommen wurden, aber es ist nicht erforderlich, die Versionierung durchzuführen.

Eine Sache, die ich ändern könnte, wenn ich sie getestet habe, ist, dass ich die zuvor erwähnte endDate-Null-Idee verwenden könnte. Um die neueste Version in meinem System zu finden, müsste ich die max (id) finden. Das andere System sucht nur nach endDate = null. Ich bin mir nicht sicher, ob die Vorteile ein anderes Datumsfeld haben.

Meine zwei Cent.


2

Während @WW. Antwort ist eine gute Antwort Eine andere Möglichkeit besteht darin, eine Versionsspalte zu erstellen und alle Ihre Versionen in derselben Tabelle zu belassen.

Für einen Tisch nähern Sie sich entweder:

  • Verwenden Sie eine Flagge, um die neueste ala Word Press anzuzeigen
  • ODER machen Sie eine böse größer als Version outer join.

Ein Beispiel für SQL der outer joinMethode unter Verwendung von Revisionsnummern ist:

SELECT tc.*
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- path in this case is our natural id.

Die schlechte Nachricht ist, dass das oben Gesagte ein erfordert outer joinund äußere Verknüpfungen langsam sein können. Die gute Nachricht ist, dass das Erstellen neuer Einträge theoretisch billiger ist, da Sie dies in einem Schreibvorgang ohne Transaktionen ausführen können (vorausgesetzt, Ihre Datenbank ist atomar).

Ein Beispiel für eine neue Revision '/stuff'könnte sein:

INSERT INTO text_content (id, path, data, revision, revision_comment, enabled, create_time, update_time)
(
SELECT
(md5(random()::text)) -- {id}
, tc.path
, 'NEW' -- {data}
, (tc.revision + 1)
, 'UPDATE' -- {comment}
, 't' -- {enabled}
, tc.create_time
, now() 
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- {path}
)

Wir fügen unter Verwendung der alten Daten ein. Dies ist besonders nützlich, wenn Sie beispielsweise nur eine Spalte aktualisieren und optimistische Sperren und / oder Transaktionen vermeiden möchten.

Der Flag-Ansatz und der Verlaufstabellen-Ansatz erfordern zwei Zeilen eingefügt / aktualisiert werden.

Der andere Vorteil des outer joinRevisionsnummernansatzes besteht darin, dass Sie später mit Triggern jederzeit auf den Mehrtabellenansatz umgestalten können, da Ihr Trigger im Wesentlichen so etwas wie das oben Genannte tun sollte.


2

Schlug Alok vor Audit table oben vor, ich würde es gerne in meinem Beitrag erklären.

Ich habe dieses schemalose Design mit einer einzelnen Tabelle in mein Projekt übernommen.

Schema:

  • id - INTEGER AUTO INCREMENT
  • Benutzername - STRING
  • Tabellenname - STRING
  • alter Wert - TEXT / JSON
  • neuer Wert - TEXT / JSON
  • Erstellt am - DATETIME

Diese Tabelle kann historische Datensätze für jede Tabelle an einem Ort enthalten, wobei der vollständige Objektverlauf in einem Datensatz enthalten ist. Diese Tabelle kann mithilfe von Triggern / Hooks gefüllt werden, bei denen sich Daten ändern und alte und neue Wertschnappschüsse der Zielzeile gespeichert werden.

Vorteile mit diesem Design:

  • Weniger Tabellen für die Verlaufsverwaltung.
  • Speichert den vollständigen Schnappschuss jeder Zeile im alten und neuen Status.
  • Einfach auf jeder Tabelle zu suchen.
  • Kann Partition nach Tabelle erstellen.
  • Kann Datenaufbewahrungsrichtlinien pro Tabelle definieren.

Nachteile mit diesem Design:

  • Die Datengröße kann groß sein, wenn das System häufig Änderungen aufweist.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.