Unveränderlichkeit im Datenbankdesign fördern


26

Einer der Punkte in Joshua Blochs Effective Java ist die Vorstellung, dass Klassen die Mutation von Instanzen so wenig wie möglich und vorzugsweise überhaupt nicht zulassen sollten.

Häufig werden die Daten eines Objekts in einer Datenbank in irgendeiner Form gespeichert. Dies hat mich dazu gebracht, über die Idee der Unveränderlichkeit in einer Datenbank nachzudenken, insbesondere für diejenigen Tabellen, die eine einzelne Entität in einem größeren System darstellen.

Mit etwas, mit dem ich in letzter Zeit experimentiert habe, möchte ich versuchen, die Aktualisierungen zu minimieren, die ich an Tabellenzeilen vornehme, die diese Objekte darstellen, und stattdessen so viel wie möglich Einfügungen durchführen.

Ein konkretes Beispiel für etwas, mit dem ich kürzlich experimentiert habe. Wenn ich weiß, dass ich später möglicherweise einen Datensatz mit zusätzlichen Daten anhängen werde, erstelle ich eine weitere Tabelle, um dies darzustellen. Diese entspricht etwa den beiden folgenden Tabellendefinitionen:

create table myObj (id integer, ...other_data... not null);
create table myObjSuppliment (id integer, myObjId integer, ...more_data... not null);

Es ist hoffentlich klar, dass diese Namen nicht wörtlich sind, sondern nur, um die Idee zu demonstrieren.

Ist dies ein vernünftiger Ansatz für die Datenpersistenzmodellierung? Lohnt es sich, Aktualisierungen für eine Tabelle einzuschränken, insbesondere um Nullen für Daten einzufügen, die möglicherweise nicht vorhanden sind, als der Datensatz ursprünglich erstellt wurde? Gibt es Zeiten, in denen ein solcher Ansatz später starke Schmerzen verursachen könnte?


7
Ich denke, dies ist eine problemlose Lösung ... Sie sollten eine Aktualisierung durchführen, anstatt aufwendige Anpassungen vorzunehmen, um eine Aktualisierung zu vermeiden.
Fosco

Ich denke, es ging eher darum, eine intuitive Idee für eine Lösung zu haben und diese von möglichst vielen Menschen ausführen zu lassen und dabei zu erkennen, dass dies möglicherweise nicht die beste Lösung für mein Problem ist. Möglicherweise stelle ich eine andere Frage zu dem Problem, sofern ich sie nicht an anderer Stelle finde.
Ed Carrel

1
Es kann gute Gründe geben, Aktualisierungen in Datenbanken zu vermeiden. Wenn diese Gründe jedoch auftreten, handelt es sich eher um ein Optimierungsproblem, das nicht ohne den Nachweis eines Problems behoben werden sollte.
Dietbuddha

6
Ich denke, es gibt ein starkes Argument für Unveränderlichkeit in Datenbanken. Es löst viele Probleme. Ich denke, die negativen Kommentare stammen nicht von aufgeschlossenen Leuten. In-Place-Updates sind die Ursache für so viele Probleme. Ich würde argumentieren, dass wir alles rückständig haben. In-Place-Updates sind die herkömmliche Lösung für ein nicht mehr vorhandenes Problem. Lagerung ist billig. Warum machen wir das? Wie viele DB-Systeme haben Audit-Protokolle, Versionsverwaltungssysteme, die für eine verteilte Replikation benötigt werden. Wie wir alle wissen, ist es erforderlich, die Latenz für die Skalierung zu unterstützen. Unveränderlichkeit löst all dies.
Cirrus

@Fosco Einige Systeme sind unbedingt erforderlich, um niemals Daten zu löschen (einschließlich der Verwendung von UPDATE). Wie die Krankenakten des Arztes.
Izkata

Antworten:


25

Der Hauptzweck der Unveränderlichkeit besteht darin, sicherzustellen, dass sich die Daten im Speicher zu keinem Zeitpunkt in einem ungültigen Zustand befinden. (Der andere Grund ist, dass mathematische Notationen zumeist statisch sind und daher unveränderliche Dinge einfacher zu konzipieren und mathematisch zu modellieren sind.) Wenn ein anderer Thread versucht, Daten zu lesen oder zu schreiben, während er bearbeitet wird, wird er möglicherweise beschädigt oder es könnte sich in einem korrupten Zustand befinden. Wenn Sie in einer Multithread-Anwendung mehrere Zuweisungsvorgänge zu den Feldern eines Objekts ausführen, versucht möglicherweise ein anderer Thread, dazwischen damit zu arbeiten - was möglicherweise schlecht ist.

Die Unveränderlichkeit behebt dies, indem zuerst alle Änderungen an eine neue Stelle im Speicher geschrieben werden und dann die endgültige Zuweisung als ein Schritt ausgeführt wird, bei dem der Zeiger auf das Objekt neu geschrieben wird, um auf das neue Objekt zu verweisen - was auf allen CPUs ein Atom ist Operation.

Datenbanken machen dasselbe mit atomaren Transaktionen : Wenn Sie eine Transaktion starten, schreibt sie alle neuen Aktualisierungen an einen neuen Ort auf der Festplatte. Wenn Sie die Transaktion abschließen, ändert sich der Zeiger auf der Festplatte an die Stelle, an der sich die neuen Aktualisierungen befinden. Dies geschieht in einem kurzen Moment, in dem andere Prozesse ihn nicht berühren können.

Dies ist auch genau das Gleiche wie Ihre Idee, neue Tabellen zu erstellen, mit der Ausnahme, dass dies automatischer und flexibler ist.

Zur Beantwortung Ihrer Frage: Ja, Unveränderlichkeit ist in Datenbanken gut, aber nein, Sie müssen nur zu diesem Zweck keine separaten Tabellen erstellen. Sie können einfach alle atomaren Transaktionsbefehle verwenden, die für Ihr Datenbanksystem verfügbar sind.


Danke für die Antwort. Diese Perspektive war genau das, was ich brauchte, um zu erkennen, dass meine Intuition verwirrend versuchte, ein paar verschiedene Ideen in einem einzigen Muster zu kombinieren.
Ed Carrel

8
Es steckt ein bisschen mehr dahinter als nur Atmosphäre. Das Argument, das ich am häufigsten für die Unveränderlichkeit in einem OOP-Kontext sehe, ist, dass unveränderliche Objekte erfordern, dass Sie ihren Zustand nur einmal im Konstruktor validieren. Wenn sie veränderbar sind, muss jede Methode, die ihren Status ändern kann, auch überprüfen, ob der resultierende Status noch gültig ist, was die Klasse erheblich komplexer machen kann. Dieses Argument gilt möglicherweise auch für Datenbanken, ist jedoch viel schwächer, da DB-Validierungsregeln eher deklarativ als prozedural sind und daher nicht für jede Abfrage dupliziert werden müssen.
Dave Sherohman

24

Dies hängt davon ab, welchen Nutzen Sie von der Unveränderlichkeit erwarten. Rei Miyasakas Antwort sprach einen an (Vermeidung ungültiger Zwischenzustände), aber hier ist ein anderer.

Mutation wird manchmal als destruktives Update bezeichnet : Wenn Sie ein Objekt mutieren, geht der alte Zustand verloren (es sei denn, Sie ergreifen zusätzliche Schritte, um es explizit zu erhalten). Im Gegensatz dazu ist es bei unveränderlichen Daten trivial, den Zustand vor und nach einer Operation gleichzeitig darzustellen oder mehrere Nachfolgezustände darzustellen. Stellen Sie sich vor, Sie möchten eine Breitensuche durch Mutieren eines einzelnen Statusobjekts implementieren.

Dies wird wahrscheinlich in der Datenbankwelt am häufigsten als zeitliche Daten angezeigt . Sagen wir letzten Monat, Sie waren im Basisplan, aber am 16. haben Sie zum Premiumplan gewechselt. Wenn wir nur ein Feld überschrieben, das angibt, auf welchem ​​Plan Sie sich befinden, können wir Probleme haben, die richtige Abrechnung zu erhalten. Es könnte uns auch die Möglichkeit fehlen, Trends zu analysieren. (Hey, schau, was diese lokale Werbekampagne gemacht hat!)

Das ist es, woran ich denke, wenn Sie "Unveränderlichkeit im Datenbankdesign" sagen.


2
Ich bin mit Ihrem dritten Absatz nicht einverstanden. Wenn Sie eine Historie (Audit-Protokoll, Protokoll der Planänderungen usw.) haben möchten, müssen Sie hierfür eine separate Tabelle erstellen. Das Duplizieren aller 50 CustomerTabellenfelder, nur um sich daran zu erinnern, dass der Benutzer den Plan geändert hat, bringt nichts als einen großen Leistungsnachteil, langsamere Auswahl über die Zeit, komplizierteres Data Mining (im Vergleich zu Protokollen) und mehr verschwendeten Speicherplatz.
Arseni Mourzenko

6
@MainMa: Vielleicht hätte ich stattdessen einfach "über temporale Datenbanken lesen" sagen sollen. Mein Beispiel sollte eine Skizze dessen sein, was zeitliche Daten sind. Ich behaupte nicht, dass dies immer die beste Art ist, sich ändernde Daten darzustellen. Auf der anderen Seite erwarte ich, dass die Unterstützung für temporäre Daten derzeit ziemlich gering ist, aber ich gehe davon aus, dass der Trend dahin geht, temporäre Daten in der Datenbank selbst unterzubringen, anstatt sie auf "zweitklassige" Darstellungen wie Änderungsprotokolle zu verweisen.
Ryan Culpepper

Was ist, wenn wir eine Änderungshistorie in einer Audittabelle führen (z. B. Spring Boot und Ruhezustand)?
Mohammad Najar

14

Wenn Sie an den Vorteilen interessiert sind, die sich aus der Unveränderlichkeit in einer Datenbank ergeben, oder zumindest an einer Datenbank, die die Illusion von Unveränderlichkeit bietet, aktivieren Sie Datomic.

Datomic ist eine Datenbank, die von Rich Hickey in Zusammenarbeit mit Think Meaning entwickelt wurde. In zahlreichen Videos werden die Architektur, die Ziele und das Datenmodell erläutert. Suchen Sie nach infoq, eine davon trägt den Titel Datomic, Database as a Value . In Confreaks finden Sie eine Keynote, die Rich Hickey auf der Euroclojure-Konferenz im Jahr 2012 hielt

Unter vimeo.com/53162418 gibt es einen Vortrag, der eher entwicklungsorientiert ist.

Hier ist eine weitere von Stuart Halloway unter .pscdn.net/008/00102/videoplatform/kv/121105techconf_close.html

  • Datomic ist eine Datenbank von Tatsachen in der Zeit, genannt Daten, in 5-Tupeln [E, A, V, T, O]
    • E Entitäts-ID
    • Ein Attributname in der Entität (kann Namespaces haben)
    • V Wert des Attributs
    • T Transaktions-ID, damit haben Sie Zeitbegriff.
    • O Eine Operation der Behauptung (aktueller oder aktueller Wert), Zurückweisung (vergangener Wert);
  • Verwendet das eigene Datenformat EDN (Extensible Data Notation)
  • Transaktionen sind ACID
  • Verwendet Datenlogger als Abfragesprache, die als SQL + -Recursive-Abfragen deklarativ ist. Abfragen werden mit Datenstrukturen dargestellt und mit Ihrer JVM-Sprache erweitert. Sie müssen keine Clojure verwenden.
  • Die Datenbank ist in 3 separate Dienste (Prozesse, Maschinen) entkoppelt:
    • Transaktion
    • Lager
    • Abfrage-Engine.
  • Sie können jeden Dienst separat skalieren.
  • Es ist kein Open Source, aber es gibt eine kostenlose (wie in Bier) Version von Datomic.
  • Sie können ein flexibles Schema angeben.
    • Reihe von Attributen ist offen
    • jederzeit neue Attribute hinzufügen
    • Keine Starrheit in der Definition oder Abfrage

Nun, da die Informationen als Fakten in der Zeit gespeichert sind:

  • Alles, was Sie tun, ist, der Datenbank Fakten hinzuzufügen. Sie löschen diese niemals (es sei denn, dies ist gesetzlich vorgeschrieben).
  • Sie können alles für immer zwischenspeichern. Query Engine, befindet sich auf dem Anwendungsserver als In-Memory-Datenbank (für JVM-Sprachen haben Nicht-JVM-Sprachen Zugriff über eine REST-API).
  • Sie können ab dem Zeitpunkt in der Vergangenheit abfragen.

Die Datenbank ist ein Wert und ein Parameter für die Abfrage-Engine. Die QE verwaltet die Verbindung und das Caching. Da Sie die Datenbank als Wert und unveränderliche Datenstruktur im Speicher sehen können, können Sie sie mit einer anderen Datenstruktur aus Werten "in der Zukunft" zusammenführen und diese mit zukünftigen Werten an die QE & Query übergeben, ohne die tatsächliche Datenbank zu ändern .

Es gibt ein Open-Source-Projekt von Rich Hickey mit dem Namen codeq . Sie finden es in github Datomic / codeq, das das Git-Modell erweitert, Verweise auf Git-Objekte in einer datenbankfreien Datenbank speichert und Abfragen Ihres Codes vornimmt Hier sehen Sie ein Beispiel für die Verwendung von Datomic.

Sie können sich datomic als ACID NoSQL vorstellen. Mit Datumsangaben können Sie Tabellen oder Dokumente oder Kv-Speicher oder Diagramme modellieren.


7

Die Idee, Aktualisierungen zu vermeiden und Einfügungen zu bevorzugen, ist einer der Gründe für den Aufbau Ihres Datenspeichers als Ereignisquelle. Diese Idee wird häufig zusammen mit CQRS verwendet. In einem Ereignisquellenmodell gibt es keine Aktualisierung: Ein Aggregat wird als Sequenz seiner "Transformation" (Ereignisse) dargestellt, und der Speicher ist daher nur anhängbar.
Diese Seite enthält interessante Diskussionen zu CQRS und Event Sourcing, wenn Sie neugierig sind!


CQRS und Event Sourcing sind in diesen Tagen ein Highlight.
Gulshan

6

Dies steht in enger Beziehung zu den sogenannten "sich langsam ändernden Dimensionen" in der Data-Warehousing-Welt und zu den "zeitlichen" oder "bi-zeitlichen" Tabellen in anderen Domänen.

Das Grundkonstrukt ist:

  1. Verwenden Sie immer einen generierten Ersatzschlüssel als Primärschlüssel.
  2. Die eindeutige Kennung dessen, was Sie beschreiben, wird zum "logischen Schlüssel".
  3. Jede Zeile sollte mindestens einen "ValidFrom" -Zeitstempel und optional einen "ValidTo" -Zeitstempel und noch optional ein "Latest Version" -Flag aufweisen.
  4. Bei der "Erstellung" einer logischen Entität fügen Sie eine neue Zeile mit einem "Gültig ab" des aktuellen Zeitstempels ein. Das optionale ValidTo wird auf "forever" (9999-12-31 23:59:59) und die letzte Version auf "True" gesetzt.
  5. Bei einer nachfolgenden Aktualisierung der logischen Entität. Sie fügen mindestens eine neue Zeile wie oben ein. Möglicherweise müssen Sie auch ValidTo in der vorherigen Version auf "now () - 1 second" und die neueste Version auf "False" einstellen.
    1. Beim logischen Löschen (dies funktioniert nur mit dem ValidTo-Zeitstempel!) Setzen Sie das ValidTo-Flag in der aktuellen Zeile auf "now () -1 second".

Der Vorteil dieses Schemas besteht darin, dass Sie den "Status" Ihrer logischen Entität zu jedem Zeitpunkt neu erstellen können, über einen längeren Zeitraum einen Verlauf Ihrer Entität haben und Konflikte minimieren können, wenn Ihre "logische Entität" stark ausgelastet ist.

Die Nachteile sind, dass Sie viel mehr Daten speichern und mehr Indizes verwalten müssen (zumindest bei Logical Key + ValidFrom + ValidTo). Ein Index für Logical Key + Latest Version beschleunigt die meisten Abfragen erheblich. Es erschwert auch Ihre SQL!

Es liegt an Ihnen, ob dies sinnvoll ist, es sei denn, Sie müssen wirklich einen Verlauf führen und den Status Ihrer Entitäten zu einem bestimmten Zeitpunkt neu erstellen.


1

Ein weiterer möglicher Grund für eine unveränderliche Datenbank wäre die Unterstützung einer besseren Parallelverarbeitung. Aktualisierungen, die nicht in der richtigen Reihenfolge durchgeführt werden, können die Daten permanent durcheinander bringen. Daher muss eine Sperrung erfolgen, um dies zu verhindern und die parallele Leistung zu beeinträchtigen. Viele Einfügungen von Ereignissen können in beliebiger Reihenfolge erfolgen, und der Zustand wird zumindest irgendwann richtig sein , solange alle Ereignisse irgendwann verarbeitet werden. Dies ist jedoch so hart zu arbeiten mit in der Praxis im Vergleich zu Datenbank - Updates zu tun , dass Sie wirklich viel Parallelität berücksichtigen müssen , die Dinge so zu tun haben würden - ich bin nicht es zu empfehlen.


0

Haftungsausschluss: Ich bin so ziemlich neu in DB: p

Davon abgesehen hat dieser Ansatz der Satellitendarstellung einen unmittelbaren Einfluss auf die Leistung:

  • Gut weniger Verkehr auf dem Primärtisch
  • Gute kleinere Zeilen in der Primärtabelle
  • Bad die Satellitendaten erfordern , bedeutet eine andere Nachschlag ist notwendig
  • Bad mehr Platz belegt, wenn alle Objekte in beiden Tabellen vorhanden sind

Abhängig von Ihren Anforderungen können Sie dies entweder begrüßen oder nicht, aber es ist sicherlich ein Punkt, den Sie berücksichtigen sollten.


-1

Ich verstehe nicht, wie Ihr Schema "unveränderlich" genannt werden kann.

Was passiert, wenn sich ein in der Ergänzungstabelle gespeicherter Wert ändert? Es sieht so aus, als müssten Sie eine Aktualisierung für diese Tabelle durchführen.

Damit eine Datenbank wirklich unveränderlich ist, muss sie ausschließlich von "INSERTS" verwaltet werden. Dazu benötigen Sie eine Methode zur Identifizierung der "aktuellen" Zeile. Dies führt fast immer zu einer schrecklichen Ineffizienz. Sie müssen entweder alle vorherigen unveränderten Werte kopieren oder den aktuellen Status aus mehreren Datensätzen zusammenfügen, wenn Sie eine Abfrage durchführen. Die Auswahl der aktuellen Zeile erfordert normalerweise etwas schrecklich chaotisches SQL wie ( where updTime = (SELECT max(updTime) from myTab where id = ?).

Dieses Problem tritt häufig in DataWarehousing auf, wo Sie einen Verlauf der Daten über einen bestimmten Zeitraum hinweg führen und den Status für einen bestimmten Zeitpunkt auswählen müssen. Die Lösung sind normalerweise "dimensionale" Tabellen. Während sie jedoch das DW-Problem "Wer war der Vertriebsmitarbeiter im letzten Januar" lösen. Sie bieten keinen der Vorteile, die unveränderliche Klassen von Java bieten.

Auf einer philosophischeren Anmerkung; Es gibt Datenbanken, in denen der Status gespeichert wird (Ihr Kontostand, Ihr Stromverbrauch, Ihre Brownie-Punkte für StackOverflow usw. usw.). Der Versuch, eine "zustandslose" Datenbank zu erstellen, scheint eine ziemlich sinnlose Aufgabe zu sein.


Für einen einzelnen Datensatz WHERE id = {} ORDER BY updTime DESC LIMIT 1 Allgemeinen nicht zu ineffizient.
Izkata

@ Izkata - versuchen Sie, in der Mitte eines Drei-Tabellen-Join setzen :-)
James Anderson
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.