Ich denke schon seit einiger Zeit darüber nach und kann mir nur zwei Möglichkeiten vorstellen, dies zu tun. Beide können vollständig transparent arbeiten, wenn sie in eine abstrakte Datenschicht/ein abstraktes Modell eingearbeitet werden.
Übrigens gibt es in der ORM-Mapper-Doktrin eine Implementierung für "versionierbare" Tabellendaten. Siehe dieses Beispiel in ihrer Dokumentation . Vielleicht passt das zu Ihren Anforderungen, aber nicht zu meinen. Es scheint, dass alle Verlaufsdaten gelöscht werden, wenn der ursprüngliche Datensatz gelöscht wird, was ihn nicht wirklich revisionssicher macht.
Option A:Halten Sie eine Kopie jeder Tabelle bereit, um Revisionsdaten zu speichern
Nehmen wir an, Sie haben eine einfache Kontakttabelle:
CREATE TABLE contact (
id INT NOT NULL auto_increment,
name VARCHAR(255),
firstname VARCHAR(255),
lastname VARCHAR(255),
PRIMARY KEY (id)
)
Sie würden eine Kopie dieser Tabelle erstellen und Revisionsdaten hinzufügen:
CREATE TABLE contact_revisions (
id INT NOT NULL,
name VARCHAR(255),
firstname VARCHAR(255),
lastname VARCHAR(255),
revision_id INT auto_increment,
type ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL,
change_time DEFAULT current_timestamp,
PRIMARY KEY(revision_id)
)
Behalten Sie INSERT
im Auge und UPDATE
mit AFTER
löst aus. Fügen Sie bei jeder neuen Datenrevision im Original eine Kopie der neuen Daten in die Revisionstabelle ein und setzen Sie die Änderung type
richtig.
Um ein DELETE
zu protokollieren revisionssicher müssen Sie auch eine neue Zeile in die History-Tabelle einfügen! Dazu sollten Sie einen BEFORE DELETE
verwenden Triggern und speichern Sie die letzten Werte, bevor sie gelöscht werden. Andernfalls müssen Sie alle NOT NULL
entfernen Einschränkung auch in der Verlaufstabelle.
Einige wichtige Hinweise zu dieser Implementierung
- Für die Verlaufstabelle müssen Sie jeden
UNIQUE KEY
löschen (hier:derPRIMARY KEY
) aus der Revisionstabelle, da Sie denselben Schlüssel mehrmals für jede Datenrevision haben werden. - Wenn Sie
ALTER
das Schema und die Daten in der Originaltabelle durch ein Update (z. B. Software-Update) zu ändern, müssen Sie sicherstellen, dass die gleichen Daten oder Schemakorrekturen auch auf die Historientabelle und ihre Daten angewendet werden. Andernfalls werden Sie Probleme bekommen, wenn Sie zu einer älteren Revision eines Datensatzes zurückkehren. - In einer realen Implementierung möchten Sie wissen, welcher Benutzer die Daten geändert hat. Um dies revisionssicher zu haben, sollte ein Benutzerdatensatz niemals aus der Benutzertabelle gelöscht werden. Sie sollten das Konto einfach mit einem Flag deaktivieren.
- Normalerweise betrifft eine einzelne Benutzeraktion mehr als eine Tabelle. In einer realen Implementierung müssten Sie auch nachverfolgen, welche Änderungen in mehreren Tabellen zu einer einzelnen Benutzertransaktion gehören und auch in welcher Reihenfolge. In einem realen Anwendungsfall möchten Sie alle Änderungen einer einzelnen Transaktion zusammen in umgekehrter Reihenfolge rückgängig machen. Das würde eine zusätzliche Revisionstabelle erfordern, die die Benutzer und Transaktionen verfolgt und eine lose Beziehung zu all diesen einzelnen Revisionen in den Verlaufstabellen hält.
Vorteile:
- vollständig in der Datenbank, unabhängig vom Anwendungscode. (Nun, nicht, wenn das Verfolgen von Benutzertransaktionen wichtig ist. Das würde etwas Logik außerhalb des Bereichs der einzelnen Abfrage erfordern)
- Alle Daten sind in ihrem Originalformat, keine impliziten Typkonvertierungen.
- gute Leistung bei der Suche in den Revisionen
- Einfacher Rollback. Machen Sie einfach ein einfaches
INSERT .. ON DUPLICATE KEY UPDATE ..
-Anweisung auf der ursprünglichen Tabelle, wobei die Daten aus der Revision verwendet werden, die Sie rückgängig machen möchten.
Verdienste:
- Manuell schwer zu implementieren.
- Schwer (aber nicht unmöglich) zu automatisieren, wenn es um Datenbankmigrationen/Anwendungsaktualisierungen geht.
Wie bereits oben erwähnt, Doktrinen versionable
macht etwas ähnliches.
Option B:Haben Sie eine zentrale Änderungsprotokolltabelle
Vorwort:Schlechte Praxis, nur zur Veranschaulichung der Alternative gezeigt.
Dieser Ansatz stützt sich stark auf Anwendungslogik, die in einer Datenschicht/einem Datenmodell verborgen sein sollte.
Sie haben eine zentrale Verlaufstabelle, die
verfolgt- Wer hat
- wann
- ändern, einfügen oder löschen
- welche Daten
- in welchem Feld
- von welcher Tabelle
Wie bei dem anderen Ansatz möchten Sie möglicherweise auch nachverfolgen, welche einzelnen Datenänderungen zu einer einzelnen Benutzeraktion / Transaktion gehören und in welcher Reihenfolge.
Vorteile:
- Beim Hinzufügen von Feldern zu einer Tabelle oder beim Erstellen einer neuen Tabelle ist keine Synchronisierung mit der ursprünglichen Tabelle erforderlich. es skaliert transparent.
Verdienste:
- schlechte Praxis mit einem einfachen Wert =Schlüsselspeicher in der Datenbank
- Schlechte Suchleistung aufgrund impliziter Typkonvertierungen
- kann die Gesamtleistung der Anwendung/Datenbank verlangsamen, wenn die zentrale Verlaufstabelle aufgrund von Schreibsperren zu einem Engpass wird (dies gilt nur für bestimmte Engines mit Tabellensperren, z. B. MyISAM)
- Es ist viel schwieriger, Rollbacks zu implementieren
- Mögliche Datenkonvertierungsfehler / Präzisionsverlust durch implizite Typkonvertierung
- verfolgt keine Änderungen, wenn Sie irgendwo in Ihrem Code direkt auf die Datenbank zugreifen, anstatt Ihre Modell-/Datenschicht zu verwenden, und vergisst, dass Sie in diesem Fall manuell in das Revisionsprotokoll schreiben müssen. Kann ein großes Problem sein, wenn Sie in einem Team mit anderen Programmierern arbeiten.
Fazit:
- Option B kann für kleine Apps als einfaches "Drop-in" sehr praktisch sein, wenn es nur um das Protokollieren von Änderungen geht.
- Wenn Sie in der Zeit zurückgehen und die Unterschiede zwischen der historischen Revision 123 leicht vergleichen möchten zu Revision 125 und/oder zu den alten Daten zurückkehren, dann Option A ist der harte Weg.