Es ist subtil.
Wenn die Geschäftsanforderung lautet "Ich möchte die Änderungen an den Daten prüfen - wer hat was und wann getan?", Können Sie normalerweise Prüftabellen verwenden (gemäß dem von Keethanjan veröffentlichten Auslösebeispiel). Ich bin kein großer Fan von Triggern, aber es hat den großen Vorteil, dass es relativ einfach zu implementieren ist - Ihr vorhandener Code muss nichts über die Trigger und das Prüfungsmaterial wissen.
Wenn die Geschäftsanforderung lautet "Zeigen Sie mir, wie der Status der Daten zu einem bestimmten Zeitpunkt in der Vergangenheit war", bedeutet dies, dass der Aspekt der zeitlichen Änderung in Ihre Lösung eingegangen ist. Sie können den Status der Datenbank zwar nur anhand von Prüftabellen rekonstruieren, dies ist jedoch schwierig und fehleranfällig, und für jede komplizierte Datenbanklogik wird sie unhandlich. Wenn das Unternehmen beispielsweise wissen möchte, "die Adressen der Briefe zu finden, die wir an Kunden hätten senden sollen, die am ersten Tag des Monats ausstehende, unbezahlte Rechnungen hatten", müssen Sie wahrscheinlich ein halbes Dutzend Audittabellen durchsuchen.
Stattdessen können Sie das Konzept der zeitlichen Änderung in Ihr Schemadesign einbinden (dies ist die zweite Option, die Keethanjan vorschlägt). Dies ist eine Änderung an Ihrer Anwendung, definitiv auf der Ebene der Geschäftslogik und der Persistenz. Sie ist also nicht trivial.
Wenn Sie beispielsweise eine Tabelle wie diese haben:
CUSTOMER
---------
CUSTOMER_ID PK
CUSTOMER_NAME
CUSTOMER_ADDRESS
und Sie wollten den Überblick behalten, Sie würden es wie folgt ändern:
CUSTOMER
------------
CUSTOMER_ID PK
CUSTOMER_VALID_FROM PK
CUSTOMER_VALID_UNTIL PK
CUSTOMER_STATUS
CUSTOMER_USER
CUSTOMER_NAME
CUSTOMER_ADDRESS
Jedes Mal, wenn Sie einen Kundendatensatz ändern möchten, anstatt den Datensatz zu aktualisieren, setzen Sie VALID_UNTIL für den aktuellen Datensatz auf NOW () und fügen einen neuen Datensatz mit einem VALID_FROM (jetzt) und einem Nullwert VALID_UNTIL ein. Sie setzen den Status "CUSTOMER_USER" auf die Login-ID des aktuellen Benutzers (falls Sie diese behalten müssen). Wenn der Kunde gelöscht werden muss, verwenden Sie das Flag CUSTOMER_STATUS, um dies anzuzeigen. Sie dürfen niemals Datensätze aus dieser Tabelle löschen.
Auf diese Weise können Sie immer den Status der Kundentabelle für ein bestimmtes Datum ermitteln - wie lautete die Adresse? Haben sie den Namen geändert? Durch Verbinden mit anderen Tabellen mit ähnlichen Daten von valid_from und valid_until können Sie das gesamte Bild historisch rekonstruieren. Um den aktuellen Status zu ermitteln, suchen Sie nach Datensätzen mit dem Datum VALID_UNTIL null.
Es ist unhandlich (genau genommen benötigen Sie das valid_from nicht, aber es erleichtert die Abfragen ein wenig). Dies erschwert Ihr Design und Ihren Datenbankzugriff. Aber es macht den Wiederaufbau der Welt viel einfacher.
Hier ist eine einfache Möglichkeit, dies zu tun:
Erstellen Sie zunächst eine Verlaufstabelle für jede Datentabelle, die Sie verfolgen möchten (Beispielabfrage unten). Diese Tabelle enthält einen Eintrag für jede Abfrage zum Einfügen, Aktualisieren und Löschen, die für jede Zeile in der Datentabelle ausgeführt wird.
Die Struktur der Verlaufstabelle entspricht der Datentabelle, die sie verfolgt, mit Ausnahme von drei zusätzlichen Spalten: einer Spalte zum Speichern der aufgetretenen Operation (nennen wir sie "Aktion"), dem Datum und der Uhrzeit der Operation sowie einer Spalte um eine Sequenznummer ('Revision') zu speichern, die pro Operation inkrementiert und nach der Primärschlüsselspalte der Datentabelle gruppiert wird.
Zu diesem Sequenzierungsverhalten wird ein zweispaltiger (zusammengesetzter) Index für die Primärschlüsselspalte und die Revisionsspalte erstellt. Beachten Sie, dass Sie die Sequenzierung nur auf diese Weise durchführen können, wenn die von der Verlaufstabelle verwendete Engine MyISAM ist ( siehe 'MyISAM-Hinweise' auf dieser Seite).
Die Verlaufstabelle ist ziemlich einfach zu erstellen. Ersetzen Sie in der unten stehenden ALTER TABLE-Abfrage (und in den darunter liegenden Trigger-Abfragen) 'primary_key_column' durch den tatsächlichen Namen dieser Spalte in Ihrer Datentabelle.
Und dann erstellen Sie die Auslöser:
Und du bist fertig. Jetzt werden alle Einfügungen, Aktualisierungen und Löschungen in 'MyDb.data' in 'MyDb.data_history' aufgezeichnet, sodass Sie eine Verlaufstabelle wie diese erhalten (abzüglich der erfundenen Spalte 'data_columns').
Um die Änderungen für eine bestimmte Spalte oder Spalten von Aktualisierung zu Aktualisierung anzuzeigen, müssen Sie die Verlaufstabelle in den Primärschlüssel- und Sequenzspalten mit sich selbst verknüpfen. Sie können zu diesem Zweck eine Ansicht erstellen, zum Beispiel:
Edit: Oh wow, Leute mögen meine Geschichtstabelle vor 6 Jahren: P.
Ich würde annehmen, dass meine Implementierung immer noch summt und größer und unhandlicher wird. Ich habe Ansichten und eine hübsche Benutzeroberfläche geschrieben, um den Verlauf in dieser Datenbank zu betrachten, aber ich glaube nicht, dass sie jemals viel verwendet wurde. So geht es.
Um einige Kommentare in keiner bestimmten Reihenfolge anzusprechen:
Ich habe meine eigene Implementierung in PHP durchgeführt, die etwas komplizierter war, und einige der in Kommentaren beschriebenen Probleme vermieden (Indizes wurden signifikant übertragen. Wenn Sie eindeutige Indizes in die Verlaufstabelle übertragen, werden die Dinge kaputt gehen. Es gibt Lösungen für dies in den Kommentaren). Diesem Beitrag auf den Brief zu folgen, könnte ein Abenteuer sein, abhängig davon, wie etabliert Ihre Datenbank ist.
Wenn die Beziehung zwischen dem Primärschlüssel und der Revisionsspalte nicht korrekt zu sein scheint, bedeutet dies normalerweise, dass der zusammengesetzte Schlüssel irgendwie verzerrt ist. In einigen seltenen Fällen hatte ich dies und war für die Sache ratlos.
Ich fand diese Lösung ziemlich performant und verwendete dabei Trigger. Außerdem ist MyISAM bei Einfügungen schnell, was alles ist, was die Trigger tun. Sie können dies durch intelligente Indizierung (oder fehlende ...) weiter verbessern. Das Einfügen einer einzelnen Zeile in eine MyISAM-Tabelle mit einem Primärschlüssel sollte kein Vorgang sein, den Sie wirklich optimieren müssen, es sei denn, Sie haben an anderer Stelle erhebliche Probleme. Während der gesamten Zeit, in der ich die MySQL-Datenbank ausführte, in der sich diese Implementierung der Verlaufstabelle befand, war dies nie die Ursache für eines der (vielen) Leistungsprobleme, die auftraten.
Wenn Sie wiederholt Einfügungen erhalten, überprüfen Sie Ihre Softwareschicht auf Abfragen vom Typ INSERT IGNORE. Hrmm, ich kann mich jetzt nicht erinnern, aber ich denke, es gibt Probleme mit diesem Schema und Transaktionen, die letztendlich fehlschlagen, nachdem mehrere DML-Aktionen ausgeführt wurden. Zumindest etwas, das man beachten sollte.
Es ist wichtig, dass die Felder in der Verlaufstabelle und der Datentabelle übereinstimmen. Oder vielmehr, dass Ihre Datentabelle nicht MEHR Spalten als die Verlaufstabelle enthält. Andernfalls schlagen Einfügen / Aktualisieren / Löschen von Abfragen in der Datentabelle fehl, wenn die Einfügungen in die Verlaufstabellen Spalten in die Abfrage einfügen, die nicht vorhanden sind (aufgrund von d. * In den Triggerabfragen), und der Trigger fehlschlägt. Es wäre fantastisch, wenn MySQL so etwas wie Schema-Trigger hätte, bei denen Sie die Verlaufstabelle ändern könnten, wenn Spalten zur Datentabelle hinzugefügt würden. Hat MySQL das jetzt? Ich reagiere heutzutage: P.
quelle
CREATE TABLE MyDB.data_history as select * from MyDB.data limit 0;
owner
Feld, und zum Aktualisieren könnte ich einupdatedby
Feld hinzufügen , aber zum Löschen bin ich mir nicht sicher, wie ich das über die Trigger tun könnte. Das Aktualisieren derdata_history
Zeile mit der Benutzer-ID in fühlt sich schmutzig an: PSie können Trigger erstellen, um dies zu lösen. Hier ist ein Tutorial dazu (archivierter Link).
Eine andere Lösung wäre, ein Revisionsfeld beizubehalten und dieses Feld beim Speichern zu aktualisieren. Sie können entscheiden, dass das Maximum die neueste Version ist oder dass 0 die letzte Zeile ist. Das liegt an dir.
quelle
So haben wir es gelöst
Eine Benutzertabelle sah so aus
Und die Geschäftsanforderungen änderten sich und wir mussten alle vorherigen Adressen und Telefonnummern überprüfen, die ein Benutzer jemals hatte. Das neue Schema sieht so aus
Um die aktuelle Adresse eines Benutzers zu finden, suchen wir nach UserData mit der Revision DESC und LIMIT 1
Um die Adresse eines Benutzers zwischen einem bestimmten Zeitraum zu erhalten, können wir created_on bewteen (Datum1, Datum 2) verwenden.
quelle
revision=1
von demid_user=1
? Zuerst hatte ich gedacht, dass Ihre Zählung war,0,2,3,...
aber dann sah ich, dass fürid_user=2
die Revisionszählung0,1, ...
id
undid_user
Spalten-. Just use a group ID of
ID` (Benutzer-ID) undrevision
.MariaDB unterstützt die Systemversionierung seit 10.3. Dies ist die Standard-SQL-Funktion, die genau das tut, was Sie möchten: Sie speichert den Verlauf von Tabellendatensätzen und ermöglicht den Zugriff darauf über
SELECT
Abfragen. MariaDB ist eine offene Entwicklungsgabel von MySQL. Weitere Informationen zur Systemversionierung finden Sie über diesen Link:https://mariadb.com/kb/en/library/system-versioned-tables/
quelle
Warum nicht einfach Bin-Protokolldateien verwenden? Wenn die Replikation auf dem MySQL-Server festgelegt ist und das Binlog-Dateiformat auf ROW festgelegt ist, können alle Änderungen erfasst werden.
Eine gute Python-Bibliothek namens noplay kann verwendet werden. Mehr Infos hier .
quelle
Nur meine 2 Cent. Ich würde eine Lösung erstellen, die genau aufzeichnet, was sich geändert hat, sehr ähnlich der Lösung von transient.
Meine Änderungstabelle wäre einfach:
DateTime | WhoChanged | TableName | Action | ID |FieldName | OldValue
1) Wenn eine ganze Zeile in der Haupttabelle geändert wird, werden viele Einträge in diese Tabelle aufgenommen, ABER das ist sehr unwahrscheinlich, also kein großes Problem (die Leute ändern normalerweise nur eine Sache) 2) OldVaue (und NewValue, wenn Sie wollen) müssen eine Art epischen "anytype" sein, da es sich um beliebige Daten handeln kann. Möglicherweise gibt es eine Möglichkeit, dies mit RAW-Typen zu tun oder einfach JSON-Strings zum Ein- und Auskonvertieren zu verwenden.
Minimale Datennutzung, speichert alles, was Sie benötigen, und kann für alle Tabellen gleichzeitig verwendet werden. Ich recherchiere das gerade selbst, aber das könnte der Weg sein, den ich gehe.
Für Erstellen und Löschen nur die Zeilen-ID, keine Felder erforderlich. Beim Löschen wäre ein Flag in der Haupttabelle (aktiv?) Gut.
quelle
Der direkte Weg, dies zu tun, besteht darin, Trigger für Tabellen zu erstellen. Legen Sie einige Bedingungen oder Zuordnungsmethoden fest. Wenn ein Update oder Löschen erfolgt, wird es automatisch in die Änderungstabelle eingefügt.
Aber der größte Teil ist, wenn wir viele Spalten und viele Tabellen haben. Wir müssen den Namen jeder Spalte jeder Tabelle eingeben. Offensichtlich ist es Zeitverschwendung.
Um dies besser zu handhaben, können wir einige Prozeduren oder Funktionen erstellen, um den Namen der Spalten abzurufen.
Wir können auch einfach das Tool des dritten Teils verwenden, um dies zu tun. Hier schreibe ich ein Java-Programm MySQL Tracker
quelle
create table like table
Ich denke, repliziert alle Spalten leicht