Ich dachte über eine ungewöhnliche Datenbankstruktur nach und fragte mich, ob jemand sie schon einmal gesehen hatte. Grundsätzlich werden 2 Datenbanken verwendet:
- Die erste Datenbank enthält nur die aktuell gültigen Daten
- Die zweite Datenbank enthält den Verlauf aller Daten, die jemals in der ersten Datenbank eingegeben, aktualisiert oder gelöscht wurden
Szenario
Ich arbeite an einem Projekt, in dem ich alles protokollieren muss, was passiert, und in dem sich die Daten häufig ändern.
Beispiel (nicht das echte)
Sie müssen das Datenbankdesign für eine Fußballliga durchführen. In dieser Liga gibt es Spieler und Mannschaften. Die Spieler wechseln oft die Mannschaft.
- Erste Anforderung : Die Datenbank muss die Informationen enthalten, die für das nächste Spiel erforderlich sind. Dies bedeutet eine Liste aller Spieler, Teams und in welchem Team sich jeder Spieler gerade befindet.
- Zweite Anforderung : Die Datenbank muss historische Werte enthalten, anhand derer Statistiken erstellt werden. Dies bedeutet die Liste aller Spieler, die Teil eines Teams waren, oder die Liste aller Teams, zu denen ein Spieler gehört hat.
Das Problem
Diese beiden Anforderungen sind ein bisschen das Gegenteil voneinander. Ich habe versucht, alles in derselben Datenbank zu tun, aber es macht keinen Sinn. Die erste Anforderung befasst sich nur mit dem "Spielen des nächsten Spiels", während sich die zweite Anforderung nur mit dem "Generieren von Statistiken" befasst.
Um alles in derselben Datenbank zu erledigen, habe ich eine Art "Nur einfügen" -Datenbank verwendet, bei der das offensichtliche weiche Löschen zum Löschen / Aktualisieren von Informationen verwendet wurde ...
Was anfangs wie eine leichte Aufgabe schien, eine Liste von Spielern, Teams und dem aktuellen Team jedes Spielers zu führen, wird plötzlich viel schwieriger. Die Anwendungslogik, die zum Spielen des nächsten Spiels erforderlich ist, ist bereits kompliziert genug, aber jetzt hat die Datenbank ein sehr wenig hilfreiches Design, bei dem die Anwendung bei jeder einzelnen Abfrage die Überprüfung "gelöscht" hinzufügen muss , um das nächste Spiel zu spielen.
Möchten Sie der Trainer sein, der "alle Spieler im Team, komm zu mir" schreit und dann 2000 Spieler auf dich zukommen. An diesem Punkt werden Sie wahrscheinlich "alle Spieler , die nicht im Team gelöscht wurden , kommen zu mir" rufen (während Sie über dieses dumme Design schwören).
Meine Schlussfolgerung
Ich habe mich gefragt, warum Sie alles in dieselbe Datenbank stellen müssen. Das weiche Löschen kann nicht nur alles schlecht protokollieren, es sei denn, Sie fügen viele Spalten hinzu (time_created, who_created_it, time_deleted, who_deleted_it), sondern erschwert auch alles. Dies verkompliziert das Datenbankdesign und das Anwendungsdesign.
Außerdem erhalte ich diese beiden Anforderungen als Teil einer einzelnen Anwendung, die nicht aufgeteilt werden kann, aber ich denke immer wieder: Dies sind zwei völlig unterschiedliche Anwendungen. Warum versuche ich alles zusammen zu machen?
Da habe ich darüber nachgedacht, die Datenbank in zwei Teile zu teilen. Eine Betriebsdatenbank, die nur zum Spielen des nächsten Spiels verwendet wird und nur die aktuell gültigen Informationen enthält, und eine historische Datenbank, die alle Informationen enthält, die jemals vorhanden waren, als sie erstellt, gelöscht wurden und wer sie ausgeführt hat.
Ziel ist es, die erste Datenbank (betriebsbereit) und die Anwendung so einfach wie möglich zu halten und gleichzeitig so viele Informationen wie möglich in der zweiten Datenbank (historisch) zu haben.
Fragen
- Haben Sie dieses Design schon einmal gesehen? Hat es einen Namen?
- Gibt es offensichtliche Fallstricke, die mir fehlen?
EDIT 2015-03-16
Aktuelle Architektur
Grundsätzlich können Sie sich die gesamte Architektur in zwei Schritten vorstellen.
Schritt 1 :
- Die Anwendung wird ausgeführt und Benutzer führen einige Aktionen aus
- Jedes Mal, wenn ein Ereignis eintritt, wird es automatisch (Audit-Lösung) in einer Ereignistabelle aufgezeichnet
- Dann wird die richtige Zeile in der Betriebsdatenbank aktualisiert
Schritt 2 :
- Ein Job liest die letzte Einfügung in die Ereignistabelle und fügt diese neuen Daten in die historische Datenbank ein.
- Benutzer fragen die historische Datenbank ab, um die benötigten Informationen abzurufen.
Nur aus der Ereignistabelle können Sie die Informationen zu jedem Zeitpunkt rekonstruieren. Das Problem ist, dass diese Ereignistabelle nicht einfach abfragbar ist. Hier setzt die historische Datenbank an. um die Daten so darzustellen, dass es einfach ist, genau das abzurufen, was wir wollen.
Zusätzliche Probleme, wenn alles in die gleichen Tabellen gestellt wird
Ich habe bereits meine Besorgnis über die zusätzliche Komplexität der Überprüfung "wird gelöscht" bei jeder Abfrage zum Ausdruck gebracht. Aber es gibt noch ein anderes Problem: Integrität .
Ich verwende häufig Fremdschlüssel und Einschränkungen , um sicherzustellen, dass die Daten in meiner Datenbank zu jedem Zeitpunkt gültig sind.
Schauen wir uns ein Beispiel an:
Einschränkung: Es kann nur einen Torhüter pro Team geben.
Es ist einfach, einen eindeutigen Index hinzuzufügen, der überprüft, ob es nur einen Torhüter pro Team gibt. Aber was passiert dann, wenn Sie den Torhüter wechseln? Sie müssen die Informationen über die vorherige noch beibehalten, aber jetzt haben Sie zwei Torhüter in denselben Teams, eine aktive und eine inaktive, was Ihrer Einschränkung widerspricht.
Sicher, es ist einfach, Ihrer Einschränkung einen Scheck hinzuzufügen, aber es ist eine andere Sache, die Sie verwalten und überlegen müssen.
Antworten:
Es kommt ziemlich oft vor, obwohl der Verlauf (manchmal als Audit-Aufzeichnungen bezeichnet) entweder in derselben Tabelle oder in einer separaten Tabelle in derselben Datenbank gespeichert ist.
Ich habe beispielsweise mit einem System gearbeitet, bei dem Aktualisierungen einer Tabelle als Einfügung implementiert wurden, auf dem alten "aktuellen" Datensatz ein Flag gesetzt war, das besagt, dass es sich um einen historischen Datensatz handelt, und der Zeitstempel, als er aktualisiert wurde in eine Spalte geschrieben.
Heute arbeite ich an einem System, bei dem jede Änderung in eine dedizierte Prüftabelle geschrieben wird und die Aktualisierung dann in der Tabelle erfolgt.
Letzteres ist skalierbarer, aber nicht so einfach generisch zu implementieren.
Der einfachste Weg, um Ihr Ziel zu erreichen, Abfragen einfach zu gestalten und nicht das Flag "Ist aktuell" hinzuzufügen, besteht darin, Leseanfragen nur über eine Ansicht oder eine gespeicherte Prozedur zuzulassen. Dann rufen Sie an und sagen "Alle Spieler holen". Der gespeicherte Prozess gibt nur aktuelle Spieler zurück (Sie können ein zweites Verfahren implementieren, um Spieler mit mehr Kontrolle darüber zurückzugeben, welche zurückgegeben werden). Dies funktioniert auch gut zum Schreiben. Eine gespeicherte Prozedur zum Aktualisieren eines Players kann dann alle erforderlichen Verlaufsdetails schreiben und den Player aktualisieren - ohne dass der Client jemals den Verlaufsmechanismus kennt. Aus diesem Grund sind gespeicherte Prozeduren besser als eine Ansicht, die nur aktuelle Player zurückgibt, da der gesamte DB-Zugriffsmechanismus beim Lesen und Schreiben gleich bleibt - alles läuft über einen Sproc ab.
quelle
Wenn Sie eine Datenbank in zwei Datenbanken aufteilen, verlieren Sie alle Vorteile relationaler Referenzen und der Überprüfung der referenziellen Integrität. Ich habe so etwas noch nie versucht, aber ich vermute, dass es ein großer Albtraum werden würde.
Ich glaube, dass der gesamte Datensatz, der ein bestimmtes System beschreibt, zu einer einzigen Datenbank gehört. Fragen der Benutzerfreundlichkeit beim Zugriff auf die Daten sind fast nie ein guter Grund, um Entscheidungen zur Datenorganisation zu treffen.
Probleme mit dem Komfort beim Zugriff auf Ihre Daten sollten gelöst werden, indem Sie alle Komfortfunktionen nutzen, die Ihr RDBMS bietet.
Anstatt eine 'aktuelle' Datenbank und eine 'historische' Datenbank zu haben, sollten Sie nur eine Datenbank haben und allen darin enthaltenen Tabellen sollte 'historisch' vorangestellt werden. Anschließend sollten Sie eine Reihe von Ansichten erstellen, eine für jede Tabelle, die Sie als "aktuell" anzeigen möchten, und jede Ansicht die historischen Zeilen herausfiltern lassen, die Sie nicht sehen möchten, und nur die aktuellen durchlassen.
Dies ist eine geeignete Lösung für Ihr Problem, da eine Komfortfunktion des RDBMS verwendet wird, um ein Komfortproblem des Programmierers zu lösen und das Datenbankdesign intakt zu lassen.
Ein Beispiel für ein Problem, auf das Sie wahrscheinlich stoßen (zu lang für einen Kommentar)
Angenommen, Sie sehen einen Bildschirm, auf dem aktuelle Informationen zu einem Team angezeigt werden, z. B. team.id = 10, team.name = "Manchester United", und klicken auf die Schaltfläche "Verlauf anzeigen". Zu diesem Zeitpunkt möchten Sie zu einem Bildschirm wechseln, auf dem historische Informationen zu demselben Team angezeigt werden. Sie nehmen also die ID 10, von der Sie wissen, dass sie in der "aktuellen" Datenbank für "Manchester United" steht, und Sie müssen hoffendass diese ID-Nummer 10 auch für "Manchester United" in der historischen Datenbank steht. Es gibt keine referenzielle Integritätsregel, die erzwingt, dass sich die ID in beiden Datenbanken auf genau dieselbe Entität bezieht. Daher verfügen Sie im Wesentlichen über zwei vollständig getrennte Datensätze mit impliziten Verbindungen, die nur bekannt sind, von diesen anerkannt werden und deren Aufrechterhaltung versprochen wird durch Code außerhalb der Datenbank.
Und dies gilt natürlich nicht nur für große Tische wie den "Teams" -Tisch, sondern auch für den kleinsten kleinen Tisch, den Sie auf der Seite haben werden, wie "Spielerpositionen: Stürmer, Mittelfeldspieler, Torhüter usw.".
Historizität in derselben Datenbank erreichen
Es gibt verschiedene Methoden, um die Historizität aufrechtzuerhalten, und obwohl sie den Rahmen dieser Frage sprengen, die im Grunde genommen die Fallstricke dieser besonderen Idee von Ihnen haben könnte, ist hier eine Idee:
Sie können eine Protokolltabelle verwalten, die einen Eintrag für jede einzelne Änderung enthält, die jemals an der Datenbank vorgenommen wurde. Auf diese Weise können alle "aktuellen" Tabellen vollständig von Daten gelöscht und durch Wiedergabe der protokollierten Änderungen vollständig rekonstruiert werden. Wenn Sie die "aktuellen" Tabellen durch Wiedergabe der Änderungen vom Beginn der Zeit bis jetzt rekonstruieren können, können Sie natürlich auch einen temporären Satz von Tabellen erstellen, um durch Wiedergabe eine Ansicht der Datenbank zu einer bestimmten Zeitkoordinate zu erhalten die Änderungen vom Beginn der Zeit bis zu dieser bestimmten Zeitkoordinate.
Dies ist als "Event Sourcing" bekannt (Artikel von Martin Fowler).
quelle
Das Wichtigste zuerst : Bietet Ihre Codebasis bereits eine saubere Trennung von Bedenken, bei denen die Geschäftslogik (Auswahl der Spieler für das nächste Spiel) von Ihrer Datenbankzugriffslogik (eine Ebene, die einfach eine Verbindung zur Datenbank herstellt und Ihre Datenstrukturen abbildet) unterschieden wird in Datenbankzeilen und umgekehrt)? Die Antwort darauf wird viel dazu beitragen, zu erklären, warum Sie damit zu tun haben:
Jetzt...
Angenommen, Sie sprechen von RDBMS, können Sie immer noch über eine bitemporale Datenbank verfügen , die alle gültigen Daten der Vergangenheit, Gegenwart und möglicherweise der Zukunft erfasst, und dann ein ausreichend robustes Datenbankzugriffsbibliothek- / ORM-Framework verwenden, um die Datenbankabfragelogik für Sie zu verwalten. Sie können sogar eine Datenbankansicht verwenden, um Ihre Auswahl zu erleichtern. Dann sollten die Geschäftslogik-Teile Ihres Codes die zugrunde liegenden zeitlichen Felder nicht kennen müssen, wodurch das oben beschriebene Problem behoben wird.
Anstatt beispielsweise eine SQL-Abfrage in Ihrer Anwendung fest zu codieren:
(Verwendung
?
als Parameterbindungen)Mit einer hypothetischen Datenbankzugriffsbibliothek können Sie möglicherweise Folgendes abfragen: (Pseudocode):
Oder verwenden Sie den Ansatz der Datenbankansicht:
Sie können dann während dieses Zeitraums nach Spielern fragen:
Wie wollen Sie, zurück zu Ihrem vorgeschlagenen Datenbankmodell , mit dem Entfernen historischer Daten aus Ihrer Betriebsdatenbank umgehen?
INSERT
Führen Sie einfach zwei ähnliche Zeilen in Ihre Betriebs- und Verlaufsdatenbanken ein und führen Sie dann eineDELETE
in der Betriebsdatenbank aus, wenn eine Benutzeraktion zum Löschen von Verlaufsdaten erfolgt?Groß denken
Wenn Sie von einer umfangreichen Datenverarbeitung sprechen, bei der Ihre 'Datenbank' eine skalierte verteilte Datenbank-Clustering- / Stream-Verarbeitungslösung ist, klingt Ihr Ansatz dem Lambda vage ähnlich (wahrscheinlich nur unter einigen der definierten Begriffe) Architektur , bei der Ihre "historischen" Daten (dh vergangene Echtzeitdaten) separat stapelweise verarbeitet werden, um die Art von Statistiken auszuführen, nach denen Sie suchen, und Ihre "betrieblichen" Daten (dh Streaming-Echtzeitdaten) weiterhin abfragbar sind Ein vordefinierter Grenzwert, bevor die Stapelverarbeitung diese beibehält. Die Grundlage dieses Ansatzes liegt jedoch mehr in den Vor- und Nachteilen aktueller Big Data-Implementierungen als in der bloßen Vereinfachung der eigenen Anwendungslogik.
bearbeiten (nach dem Bearbeiten des OP)
Ich hätte früher darauf antworten sollen, aber trotzdem:
Dies liegt im Allgemeinen daran, dass Endbenutzer in der Regel in Bezug auf Funktionen und nicht in Bezug auf die Anzahl der erforderlichen Datenbanken denken .
Sie erwähnen auch, dass:
Großartig! Jetzt haben Sie also eine Ereignistabelle, von der ich annehme, dass sie das in den anderen Antworten erwähnte Event-Sourcing- Konzept ist. Was liest jedoch Ihre Ereignistabelle und aktualisiert die richtige Zeile in der Betriebsdatenbank ? Entspricht das dem Job, der das letzte Ereignis liest und in die historische Datenbank einfügt ?
Noch ein Punkt zu Ihrem Einschränkungsbeispiel:
Bezieht sich " irgendein Zeitpunkt " auf eine gültige Zeit oder Transaktionszeit ?
Was passiert, wenn wir einen dritten neuen Torhüter haben? Erstellen Sie eine eindeutige Einschränkung für zeitliche Felder in der historischen Datenbank, um die Daten für zwei "alte" Torhüter gültig zu halten?
quelle
Dies ähnelt der Art und Weise, wie Datenbanktransaktionen im Allgemeinen implementiert werden, mit der Ausnahme, dass die historischen Daten normalerweise weggeworfen werden, nachdem sie in die Betriebsdatenbank geschrieben wurden. Das nächste Programmiermuster, an das ich denken kann, ist Event Sourcing .
Ich denke, die Aufteilung dieser beiden Datenbanken ist der richtige Schritt. Insbesondere würde ich die "betriebliche" Datenbank als Cache betrachten, da die historischen Daten ausreichen, um die betriebliche Datenbank jederzeit neu zu erstellen. Abhängig von der Art Ihrer Anwendung und den Leistungsanforderungen kann es nicht erforderlich sein, diesen Cache als separate Datenbank zu verwalten, wenn es sinnvoll ist, den aktuellen Status bei jedem Programmstart aus den historischen Daten im Speicher zu rekonstruieren.
In Bezug auf Fallstricke besteht das Hauptproblem darin, dass Sie irgendeine Art von Parallelität benötigen (entweder im selben Programm oder indem mehrere Clients gleichzeitig die Datenbank verwenden). In diesem Fall möchten Sie sicherstellen, dass die Änderungen an den historischen und betrieblichen Datenbanken atomar vorgenommen werden. Im Falle einer Parallelität innerhalb desselben Programms ist Ihre beste Wahl wahrscheinlich eine Art Sperrmechanismus. Für mehrere Clients, die mit derselben Datenbank interagieren, besteht der einfachste Weg darin, beide als Tabellen in derselben Datenbank zu speichern und Transaktionen zu verwenden, um die Datenbank konsistent zu halten.
quelle
Die Unterstützung der Datenversionierung in Datenbanken ist ein etabliertes Thema, und einige DBMS unterstützen diese Funktion. Ich erinnere mich, dass ich gelesen habe, dass MariaDB die Datenversionierung unterstützt ( https://mariadb.com/resources/blog/automatic-data-versioning-in-mariadb-server-10-3/ ) und eine schnelle Suche etwas namens OrpheusDB ( https: / /medium.com/data-people/painless-data-versioning-for-collaborative-data-science-90cf3a2e279d )
quelle