Alles mit Elixier und Mnesia aufbewahren

In einem meiner vorherigen Artikel habe ich über Erlang-Term-Storage-Tabellen (oder einfach ETS) geschrieben, mit denen Tupel beliebiger Daten im Speicher gespeichert werden können. Wir haben auch diskettenbasierte ETS (DETS) besprochen, die etwas eingeschränktere Funktionen bieten, aber Sie können Ihre Inhalte in einer Datei speichern.

Manchmal benötigen Sie jedoch eine noch leistungsfähigere Lösung zum Speichern der Daten. Lernen Sie Mnesia kennen - ein in Erlang erstmals eingeführtes verteiltes Datenbankmanagementsystem in Echtzeit. Mnesia verfügt über ein relationales / Objekt-Hybrid-Datenmodell und bietet viele nützliche Funktionen, darunter Replikation und schnelle Datensuche.

In diesem Artikel erfahren Sie:

  • So erstellen Sie ein Mnesia-Schema und starten das gesamte System.
  • Welche Tabellentypen sind verfügbar und wie werden sie erstellt?.
  • Wie werden CRUD-Operationen durchgeführt und was ist der Unterschied zwischen den Funktionen "Dirty" und "Transaction"?.
  • So ändern Sie Tabellen und fügen sekundäre Indizes hinzu.
  • Verwendung des Amnesia-Pakets zur Vereinfachung der Arbeit mit Datenbanken und Tabellen.

Lass uns anfangen, sollen wir?

Einführung in Mnesia

Wie bereits erwähnt, ist Mnesia also ein objekt- und relationales Datenmodell, das wirklich gut skaliert. Es hat eine DMBS-Abfragesprache und unterstützt atomare Transaktionen wie jede andere beliebte Lösung (z. B. Postgres oder MySQL). Mnesias Tabellen können auf der Festplatte und im Arbeitsspeicher gespeichert werden, Programme können jedoch ohne Kenntnis des tatsächlichen Speicherorts geschrieben werden. Darüber hinaus können Sie Ihre Daten über mehrere Knoten hinweg replizieren. Beachten Sie auch, dass Mnesia in derselben BEAM-Instanz wie der andere Code ausgeführt wird.

Da Mnesia ein Erlang-Modul ist, sollten Sie über ein Atom darauf zugreifen:

: Mnesia

Es ist jedoch möglich, einen Alias ​​wie folgt zu erstellen:

Alias: Mnesia, wie: Mnesia

Daten in Mnesia sind in organisiert Tische deren eigene Namen als Atome dargestellt werden (was der ETS sehr ähnlich ist). Die Tabellen können einen der folgenden Typen haben:

  • :einstellen-der Standardtyp. Sie können nicht mehrere Zeilen mit genau demselben Primärschlüssel haben (wir werden gleich sehen, wie Sie einen Primärschlüssel definieren). Die Reihen werden nicht auf eine bestimmte Art und Weise angeordnet.
  • : order_set-gleich wie :einstellen, Die Daten werden jedoch nach dem Primärschlüssel sortiert. Später werden wir sehen, dass sich einige Leseoperationen anders verhalten werden : order_set Tische.
  • :Tasche-Mehrere Zeilen können denselben Schlüssel haben, die Zeilen können jedoch immer noch nicht vollständig identisch sein.

Tabellen haben andere Eigenschaften, die in den offiziellen Dokumenten gefunden werden können (einige werden im nächsten Abschnitt besprochen). Bevor Sie jedoch mit dem Erstellen von Tabellen beginnen, benötigen Sie ein Schema. Fahren wir mit dem nächsten Abschnitt fort und fügen Sie eines hinzu.

Schema und Tabellen erstellen

Um ein neues Schema zu erstellen, verwenden wir eine Methode mit einem ziemlich nicht überraschenden Namen: create_schema / 1. Im Grunde wird für uns eine neue Datenbank auf einer Platte erstellt. Es akzeptiert einen Knoten als Argument:

: mnesia.create_schema ([node ()])

Ein Knoten ist eine Erlang-VM, die für die Kommunikation, den Speicher und anderes zuständig ist. Knoten können sich miteinander verbinden, und sie sind nicht auf einen PC beschränkt. Sie können sich auch über das Internet mit anderen Knoten verbinden.

Nachdem Sie den obigen Code ausgeführt haben, heißt ein neues Verzeichnis Mnesia.nonode@nohost wird erstellt, die Ihre Datenbank enthalten wird. nonode @ nohost ist hier der Name des Knotens. Bevor wir jedoch Tabellen erstellen können, muss Mnesia gestartet werden. Dies ist so einfach wie das Aufrufen der Start / 0 Funktion:

: mnesia.start ()

Mnesia sollte auf allen teilnehmenden Knoten gestartet werden, von denen jeder normalerweise einen Ordner hat, in den die Dateien geschrieben werden (in unserem Fall hat dieser Ordner einen Namen) Mnesia.nonode@nohost). Alle Knoten, aus denen das Mnesia-System besteht, werden in das Schema geschrieben. Später können Sie einzelne Knoten hinzufügen oder entfernen. Beim Start tauschen Knoten außerdem Schemainformationen aus, um sicherzustellen, dass alles in Ordnung ist.

Wenn Mnesia erfolgreich gestartet wurde, eine :OK atom wird als Ergebnis zurückgegeben. Sie können das System später anrufen, indem Sie anrufen Stop / 0:

: mnesia.stop () # =>: gestoppt

Jetzt können wir eine neue Tabelle erstellen. Zumindest sollten wir seinen Namen und eine Liste von Attributen für die Datensätze angeben (stellen Sie sich diese als Spalten vor):

: mnesia.create_table (: Benutzer, [Attribute: [: ID,: Name,: Nachname]]]) # => : atomar,: ok

Wenn das System nicht läuft, wird die Tabelle nicht erstellt und ein : abgebrochen, : node_not_running,: nonode @ nohost Fehler wird stattdessen zurückgegeben. Wenn die Tabelle bereits vorhanden ist, erhalten Sie eine : abgebrochen, : already_exists,: user Error.

So heißt unsere neue Tabelle :Nutzer, und es hat drei Attribute: :Ich würde, :Name, und :Nachname. Beachten Sie, dass das erste Attribut in der Liste immer als Primärschlüssel verwendet wird. Mit diesem Attribut können Sie schnell nach einem Datensatz suchen. Später in diesem Artikel erfahren Sie, wie Sie komplexe Abfragen schreiben und Sekundärindizes hinzufügen.

Denken Sie auch daran, dass der Standardtyp für die Tabelle ist :einstellen, Dies kann jedoch leicht geändert werden:

: mnesia.create_table (: Benutzer, [Attribute: [: ID,: Name,: Nachname], Typ:: Bag])

Sie können Ihre Tabelle sogar schreibgeschützt machen, indem Sie die Option :Zugriffsmodus zu :schreibgeschützt:

: mnesia.create_table (: Benutzer, [Attribute: [: ID,: Name,: Nachname], Typ:: Bag, Zugriffsmodus: Read_only])

Nachdem das Schema und die Tabelle erstellt wurden, hat das Verzeichnis eine schema.DAT Datei sowie einige .Log Dateien. Fahren wir nun mit dem nächsten Abschnitt fort und fügen Sie einige Daten in unsere neue Tabelle ein!

Schreibvorgänge

Um einige Daten in einer Tabelle zu speichern, müssen Sie eine Funktion verwenden schreiben / 1. Fügen wir beispielsweise einen neuen Benutzer namens John Doe hinzu:

: mnesia.write (: user, 1, "John", "Doe")

Beachten Sie, dass wir sowohl den Namen der Tabelle als auch alle Attribute des Benutzers zum Speichern angegeben haben. Versuchen Sie, den Code auszuführen ... und es schlägt fehl mit einem : abgebrochen,: no_transaction Error. Warum passiert dies? Nun, das liegt daran schreiben / 1 Funktion sollte in einer Transaktion ausgeführt werden. Wenn Sie aus irgendeinem Grund nicht bei einer Transaktion bleiben möchten, wird der Schreibvorgang möglicherweise auf "schmutzige Weise" ausgeführt dirty_write / 1:

: mnesia.dirty_write (: Benutzer, 1, "John", "Doe") # =>: ok

Dieser Ansatz wird normalerweise nicht empfohlen, erstellen wir stattdessen eine einfache Transaktion mit Hilfe von Transaktion Funktion:

: mnesia.transaction (fn ->: mnesia.write (: Benutzer, 1, "John", "Doe") end) # => : atomic,: ok

Transaktion akzeptiert eine anonyme Funktion mit einer oder mehreren gruppierten Operationen. Beachten Sie, dass in diesem Fall das Ergebnis lautet : atomic,: ok, nicht nur :OK wie war es mit dem dirty_write Funktion. Der Hauptvorteil hierbei ist, dass alle Vorgänge rückgängig gemacht werden, wenn während der Transaktion etwas schief geht.

Tatsächlich ist dies ein Atomitätsprinzip, das besagt, dass entweder alle Operationen ausgeführt werden sollten oder im Fehlerfall keine Operationen. Nehmen Sie beispielsweise an, Sie zahlen Ihren Angestellten ihre Gehälter, und plötzlich geht etwas schief. Die Operation wird gestoppt, und Sie möchten definitiv nicht in einer Situation enden, in der einige Mitarbeiter ihr Gehalt bekamen und andere nicht. Dann sind atomare Transaktionen sehr praktisch.

Das Transaktion Funktion kann so viele Schreibvorgänge haben wie nötig: 

write_data = fn ->: mnesia.write (: Benutzer, 2, "Kate", "Brown"): mnesia.write (: Benutzer, 3, "Will", "Smith") Ende: mnesia.transaction (write_data) # => : atomic,: ok

Interessanterweise können Daten mit dem aktualisiert werden schreiben funktionieren auch. Geben Sie einfach den gleichen Schlüssel und die neuen Werte für die anderen Attribute an:

update_data = fn ->: mnesia.write (: Benutzer, 2, "Kate", "Smith"): mnesia.write (: Benutzer, 3, "Will", "Brown") Ende: mnesia.transaction (Daten aktualisieren)

Beachten Sie jedoch, dass dies nicht für die Tabellen von funktioniert :Tasche Art. Da bei solchen Tabellen mehrere Datensätze denselben Schlüssel haben, erhalten Sie einfach zwei Datensätze: [: Benutzer, 2, "Kate", "Brown", : Benutzer, 2, "Kate", "Smith"]. Immer noch, :Tasche Tabellen lassen nicht zu, dass völlig identische Datensätze vorhanden sind.

Operationen lesen

Okay, jetzt, da wir einige Daten in unserer Tabelle haben, warum versuchen wir nicht, sie zu lesen? Genau wie bei Schreibvorgängen können Sie das Lesen entweder auf "schmutzige" oder "transaktionale" Weise durchführen. Der "schmutzige Weg" ist natürlich einfacher (aber das ist die dunkle Seite der Macht, Luke!):

: mnesia.dirty_read (: user, 2) # => [: user, 2, "Kate", "Smith"]

So dirty_read Gibt eine Liste der gefundenen Datensätze basierend auf dem angegebenen Schlüssel zurück. Wenn der Tisch ein ist :einstellen oder ein : order_set, Die Liste hat nur ein Element. Zum :Tasche Tabellen kann die Liste natürlich mehrere Elemente enthalten. Wenn keine Datensätze gefunden wurden, wäre die Liste leer.

Versuchen wir nun, dieselbe Operation auszuführen, jedoch den Transaktionsansatz zu verwenden:

read_data = fn ->: mnesia.read (: user, 2) end: mnesia.transaction (read_data) => : atomic, [: user, 2, "Kate", "Brown"]

Großartig!

Gibt es noch andere nützliche Funktionen zum Lesen von Daten? Aber natürlich! Beispielsweise können Sie den ersten oder den letzten Datensatz der Tabelle abrufen:

: mnesia.dirty_first (: user) # => 2: mnesia.dirty_last (: user) # => 2

Beide dirty_first und dirty_last haben ihre Transaktionspartner, nämlich zuerst und zuletzt, das sollte in einer Transaktion verpackt werden. Alle diese Funktionen geben den Schlüssel des Datensatzes zurück. Beachten Sie jedoch, dass wir in beiden Fällen erhalten 2 Als Ergebnis haben wir zwei Datensätze mit den Schlüsseln 2 und 3. Warum passiert dies?

Es scheint das für die :einstellen und :Tasche Tabellen, die dirty_first und dirty_last (ebenso gut wie zuerst und zuletzt) Funktionen sind Synonyme, da die Daten nicht in einer bestimmten Reihenfolge sortiert sind. Wenn Sie jedoch eine haben : order_set In der Tabelle werden die Datensätze nach ihren Schlüsseln sortiert, und das Ergebnis lautet:

: mnesia.dirty_first (: user) # => 2: mnesia.dirty_last (: user) # => 3

Es ist auch möglich, den nächsten oder vorherigen Schlüssel mit zu greifen dirty_next und dirty_prev (oder Nächster und vorheriges):

: mnesia.dirty_next (: user, 2) => 3: mnesia.dirty_next (: user, 3) =>: "$ end_of_table"

Wenn keine Datensätze mehr vorhanden sind, ein spezielles Atom : "$ end_of_table" ist zurück gekommen. Auch wenn der Tisch ein ist :einstellen oder :Tasche, dirty_next und dirty_prev sind Synonyme.

Schließlich können Sie alle Schlüssel aus einer Tabelle erhalten, indem Sie verwenden dirty_all_keys / 1 oder all_keys / 1:

: mnesia.dirty_all_keys (: user) # => [3, 2]

Operationen löschen

Um einen Datensatz aus einer Tabelle zu löschen, verwenden Sie dirty_delete oder löschen:

: mnesia.dirty_delete (: user, 2) # =>: ok

Dadurch werden alle Datensätze mit einem bestimmten Schlüssel entfernt.

Ebenso können Sie die gesamte Tabelle entfernen:

: mnesia.delete_table (: user)

Es gibt kein "schmutziges" Gegenstück für diese Methode. Nachdem eine Tabelle gelöscht wurde, können Sie natürlich nichts schreiben und eine : abgebrochen, : no_exists,: user Fehler wird stattdessen zurückgegeben.

Wenn Sie wirklich in einer Stimmung zum Löschen sind, können Sie das gesamte Schema mithilfe von entfernen delete_schema / 1:

: mnesia.delete_schema ([node ()])

Diese Operation gibt eine zurück : error, 'Mnesia wird nicht überall gestoppt', [: nonode @ nohost] Fehler, wenn Mnesia nicht gestoppt wird, also nicht vergessen:

: mnesia.stop (): mnesia.delete_schema ([node ()])

Komplexere Lesevorgänge

Nun, da wir die Grundlagen der Arbeit mit Mnesia kennengelernt haben, wollen wir uns etwas näher mit dem Thema befassen, um erweiterte Abfragen zu schreiben. Erstens gibt es match_object und dirty_match_object Funktionen, die zum Suchen nach einem Datensatz basierend auf einem der angegebenen Attribute verwendet werden können:

: mnesia.dirty_match_object (: Benutzer,: _, "Kate", "Brown") # => [: Benutzer, 2, "Kate", "Brown"]

Die Attribute, die Sie nicht interessieren, sind mit gekennzeichnet : _ Atom. Sie können nur den Nachnamen angeben, zum Beispiel:

: mnesia.dirty_match_object (: Benutzer,: _,: _, "Brown") # => [: Benutzer, 2, "Kate", "Brown"]

Sie können auch benutzerdefinierte Suchkriterien angeben wählen und dirty_select. Um dies in Aktion zu sehen, füllen wir zuerst die Tabelle mit den folgenden Werten auf:

write_data = fn ->: mnesia.write (: user, 2, "Kate", "Brown"): mnesia.write (: user, 3, "Will", "Smith"): mnesia.write ( : user, 4, "Will", "Smoth"): mnesia.write (: user, 5, "Will", "Smath") Ende: mnesia.transaction (write_data)

Jetzt möchte ich alle Datensätze finden, die vorhanden sind Wille als Name und deren Schlüssel sind kleiner als 5, Das bedeutet, dass die resultierende Liste nur "Will Smith" und "Will Smoth" enthalten sollte. Hier ist der entsprechende Code:

: mnesia.dirty_select (: Benutzer, [: Benutzer,: "$ 1"): "$ 2",: "$ 3", [:<, :"$1", 5, :==, :"$2", "Will" ], [:"$$"] ] ) # => [3, "Will", "Smith"], [4, "Will", "Smoth"]]

Die Dinge sind hier etwas komplexer, lassen Sie uns diesen Ausschnitt also Schritt für Schritt besprechen.

  • Erstens haben wir die : user,: "$ 1",: "$ 2",: "$ 3" Teil. Hier geben wir den Tabellennamen und eine Liste der Positionsparameter an. Sie sollten in dieser seltsam aussehenden Form geschrieben werden, damit wir sie später verwenden können. 1 US-Dollar entspricht dem :Ich würde, 2 $ ist der Name, und 3 $ ist der Nachname.
  • Als nächstes gibt es eine Liste von Schutzfunktionen, die auf die angegebenen Parameter angewendet werden sollten. :<, :"$1", 5 bedeutet, dass wir nur die Datensätze auswählen möchten, deren Attribut als markiert ist 1 US-Dollar (das ist, :Ich würde) ist weniger als 5: ==,: "$ 2", "Will", wiederum bedeutet, dass wir die Datensätze mit auswählen :Name einstellen "Wille".
  • zuletzt, [: "$$"] bedeutet, dass wir alle Felder in das Ergebnis aufnehmen möchten. Du könntest sagen [: "2 $"] um nur den Namen anzuzeigen. Beachten Sie übrigens, dass das Ergebnis eine Liste von Listen enthält: [3, "Will", "Smith"], [4, "Will", "Smoth"]].

Sie können einige Attribute auch als diejenigen kennzeichnen, die Sie nicht verwenden möchten : _ Atom. Lassen Sie uns beispielsweise den Nachnamen ignorieren:

: mnesia.dirty_select (: Benutzer, [: Benutzer,: "$ 1"): "$ 2",: _, [:<, :"$1", 5, :==, :"$2", "Will" ], [:"$$"] ] ) # => [3, "Will"], [4, "Will"]]

In diesem Fall wird der Nachname jedoch nicht in das Ergebnis aufgenommen.

Ändern der Tabellen

Transformationen durchführen

Nehmen wir an, wir möchten unsere Tabelle durch Hinzufügen eines neuen Feldes ändern. Dies kann mit der transform_table function, die den Namen der Tabelle akzeptiert, eine auf alle Datensätze anzuwendende Funktion und die Liste der neuen Attribute:

: mnesia.transform_table (: user, fn (: user, id, name, vorname) -> : user, id, name, familienname: rand.uniform (1000) end, [: id,: name, : Nachname, Gehalt])

In diesem Beispiel fügen wir ein neues Attribut namens hinzu :Gehalt (wird im letzten Argument angegeben). Wie für die Transformationsfunktion (das zweite Argument), setzen wir dieses neue Attribut auf einen zufälligen Wert. Sie können auch jedes andere Attribut innerhalb dieser Transformationsfunktion ändern. Diese Änderung der Daten wird als "Migration" bezeichnet. Dieses Konzept sollte Entwicklern aus der Rails-Welt vertraut sein.

Jetzt können Sie einfach Informationen über die Attribute der Tabelle mithilfe von verwenden table_info:

: mnesia.table_info (: Benutzer,: Attribute) # => [: ID,: Name,: Nachname,: Gehalt]

Das :Gehalt Attribut ist da! Und natürlich sind auch Ihre Daten vorhanden:

: mnesia.dirty_read (: user, 2) # => [: user, 2, "Kate", "Brown", 778]

Sie können ein etwas komplexeres Beispiel für die Verwendung der beiden finden Tabelle erstellen und transform_table Funktionen auf der ElixirSchool-Website.

Indizes hinzufügen

Mit Mnesia können Sie jedes indizierte Attribut mithilfe von add_table_index Funktion. Zum Beispiel machen wir unser :Nachname Attribut indiziert:

: mnesia.add_table_index (: Benutzer,: Nachname) # => : atomic,: ok

Wenn der Index bereits vorhanden ist, erhalten Sie eine Fehlermeldung : abgebrochen, : already_exists,: user, 4.

Wie in der Dokumentation zu dieser Funktion angegeben, sind Indizes nicht kostenlos. Insbesondere nehmen sie zusätzlichen Platz ein (proportional zur Tabellengröße) und machen das Einfügen etwas langsamer. Auf der anderen Seite können Sie schneller nach den Daten suchen. Dies ist ein fairer Kompromiss.

Sie können nach einem indizierten Feld mit dem Feld suchen dirty_index_read oder index_read Funktion:

: mnesia.dirty_index_read (: Benutzer, "Smith",: Nachname) # => [: Benutzer, 3, "Will", "Smith"]

Hier verwenden wir den Sekundärindex :Nachname um einen Benutzer zu suchen. 

Amnesia verwenden

Es kann etwas umständlich sein, direkt mit dem Mnesia-Modul zu arbeiten. Glücklicherweise gibt es jedoch ein Drittanbieter-Paket namens Amnesia (duh!), Mit dem Sie triviale Operationen einfacher ausführen können.

Sie können beispielsweise Ihre Datenbank und eine Tabelle wie folgt definieren:

use amnesia defdatabase Demo duktiver Benutzer, [: id, autoincrement,: name,: Nachname,: email], Index: [: email] do end end

Dies wird eine Datenbank definieren, die aufgerufen wird Demo mit einem Tisch Nutzer. Der Benutzer benennt einen Namen, einen Nachnamen, eine E-Mail (ein indiziertes Feld) und eine ID (Primärschlüssel wird auf Autoincrement gesetzt)..

Als Nächstes können Sie das Schema einfach mit der integrierten Mischaufgabe erstellen:

mix amnesia.create -d Demo --disk

In diesem Fall ist die Datenbank festplattenbasiert. Es gibt jedoch noch weitere Optionen, die Sie festlegen können. Es gibt auch eine Drop-Task, die natürlich die Datenbank und alle Daten zerstört:

mix amnesia.drop -d Demo

Es ist möglich, sowohl die Datenbank als auch das Schema zu zerstören:

mix amnesia.drop -d Demo --schema

Wenn Datenbank und Schema vorhanden sind, können verschiedene Operationen für die Tabelle ausgeführt werden. Erstellen Sie beispielsweise einen neuen Datensatz:

Amnesia.transaction do will_smith =% Benutzer Name: "Will", Nachname: "Smith", E-Mail: "[email protected]" |> User.write end

Oder holen Sie sich einen Benutzer per ID:

Amnesia.transaction do will_smith = User.read (1) end

Darüber hinaus können Sie eine definieren Botschaft Tabelle, während eine Beziehung zum Nutzer Tisch mit einem Benutzeridentifikation als Fremdschlüssel:

deftable Nachricht, [: user_id,: content] wird beendet

Die Tabellen können eine Reihe von Hilfsfunktionen enthalten, z. B. zum Erstellen einer Nachricht oder zum Abrufen aller Nachrichten:

Benutzer, [: id, autoincrement,: name,: Nachname,: email], Index: [: email] do def add_message (self, content) do% Nachricht user_id: self.id, content: content | > Message.write end def messages (self) Ende von Message.read (self.id)

Sie können jetzt den Benutzer finden, eine Nachricht für ihn erstellen oder alle seine Nachrichten mit Leichtigkeit auflisten:

Amnesia.transaction do will_smith = User.read (1) will_smith |> User.add_message "hi!" will_smith |> User.messages enden

Ganz einfach, nicht wahr? Einige andere Anwendungsbeispiele finden Sie auf der offiziellen Website von Amnesia.

Fazit

In diesem Artikel haben wir über das Mnesia-Datenbankverwaltungssystem für Erlang und Elixir gesprochen. Wir haben die Hauptkonzepte dieses DBMS erörtert und gesehen, wie man ein Schema, eine Datenbank und Tabellen erstellt und alle wichtigen Vorgänge ausführt: Erstellen, Lesen, Aktualisieren und Zerstören. Außerdem haben Sie gelernt, wie Sie mit Indizes arbeiten, Tabellen transformieren und wie Sie das Amnesia-Paket verwenden, um die Arbeit mit Datenbanken zu vereinfachen.

Ich hoffe wirklich, dass dieser Artikel nützlich war und Sie möchten Mnesia auch in Aktion ausprobieren. Wie immer danke ich Ihnen, dass Sie bei mir geblieben sind und bis zum nächsten Mal!