Wie man schon Transaktionen in MongoDB fühlt

Published on July 13, 2018

Wie man schon Transaktionen in MongoDB fühlt

Im Sommer 2018 (also zum Zeitpunkt dieses Schreibens) geschah etwas Unglaubliches: Ehrliche ACID-Transaktionen wurden an MongoDB übergeben . Mit der Veröffentlichung der vierten Version dieses dokumentenorientierten DBMS kann es für etwas ernstere Anwendungen verwendet werden.


Für diejenigen, die sich im Tank befinden: Mit Transaktionen können wir eine Reihe von Änderungen in mehreren Dokumenten vornehmen und diese gleichzeitig speichern oder alle Änderungen, die im Rahmen einer Transaktion vorgenommen wurden, auf einmal rückgängig machen, wenn ein Fehler aufgetreten ist oder die Anwendung fehlgeschlagen ist .


Leider ist es dem Entwickler nicht so einfach, diese Super-Funktion zu nutzen. Im Folgenden werde ich Ihnen erklären, warum und was Sie dagegen tun müssen.


Wenn Sie die DBMS-Dokumentation im Abschnitt " Transaktionen" öffnen , wird der folgende Hinweis angezeigt:


Transaktionen mit mehreren Dokumenten sind nur für Replikatsätze verfügbar. Transaktionen für Sharded-Cluster sind für MongoDB 4.2 geplant

Dies zeigt uns, dass ein einfacher MongoDB-Server keine Transaktionen unterstützt, nur einen Cluster im Replikatsatzmodus . Unterstützung in Sharded- Clustern wird auch später in Version 4.2 verfügbar sein.


Gleichzeitig können wir auf einem normalen Server eine Transaktion starten, speichern oder abbrechen. Es ist jedoch nicht möglich, darin etwas zu tun. Folgendes wird angezeigt:


WriteCommandError({
  "ok" : 0,
  "errmsg" : "Transaction numbers are only allowed on a replica set member or mongos",
  "code" : 20,
  "codeName" : "IllegalOperation"
})

Glücklicherweise kann jeder einen MongoDB-Cluster ausführen, der aus einem Server besteht. Auf meinen eigenen Maschinen, auf denen ich entwickle, starte ich alle DBMS in Docker- Containern. Das Starten eines regulären MongoDB-Servers sieht beispielsweise so aus:


docker run -v ~/mongo/:/data/db --name mongo --restart=always -p 27017:27017 -d mongo mongod --smallfiles

Lassen Sie uns die Startschlüssel sortieren:


  • -v ~ / mongo /: / data / db bedeutet, dass das lokale Verzeichnis ~ / mongo / in / data / db des Containers gemountet wird , sodass die Basis selbst auf dem Hostcomputer gespeichert wird, wodurch wir den laufenden Container löschen, Versionen aktualisieren usw. können. d. mit der Bewahrung unserer Daten;
  • --name mongo setzt den Namen des Containers;
  • --restart = bedeutet immer , dass im Falle eines Dienstabsturzes im Container ein Neustart durchgeführt und der Container nach dem Laden des Betriebssystems gestartet werden sollte.
  • -p 27017: 27017 " pusht " den Port zum Hostrechner;
  • -d gibt an, dass der Container als Daemon gestartet werden soll.
  • mongo  - der Name des Bildes, auf dem der Container ausgeführt werden soll;
  • mongod  - smallfiles - der Befehl zum Starten des Dienstes im Container.

Wie man einen einfachen Server betreibt, habe ich nur als Referenz angeführt. Lassen Sie uns nun herausfinden, was getan werden muss, um einen Server zu starten, der Transaktionen unterstützt.


Zunächst sollten Sie im Docker ein neues Netzwerk erstellen, in dem alle Server unseres Clusters funktionieren. Ja, ich habe oben geschrieben, dass der Server einer sein wird, aber das Netzwerk muss erstellt werden, sonst funktioniert es nicht.


docker network create mongo-cluster

Als Nächstes müssen Sie in den Containerstartparametern die Verwendung des neuen Netzwerks - net mongo-cluster angeben und den Parameter auch an den Server übertragen, um im Replikatsatzmodus zu arbeiten : - replSet rs0 . Außerdem habe ich absichtlich die Taste --restart = always gedrückt , da Ich verwende MongoDB jetzt nicht immer bei der Arbeit und möchte nicht, dass es mit dem Betriebssystem beginnt.


docker run -v ~/mongo/:/data/db --name mongo -p 27017:27017 -d mongo mongod --smallfiles --replSet rs0

Großartig, der Container läuft, was wir überprüfen können, indem wir den docker ps Befehl ausführen und so etwas sehen:


docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                      NAMES
2292d7e0778b        mongo               "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:27017->27017/tcp   mongo

Als nächstes müssen wir den Cluster initialisieren, dazu die Konsole des laufenden Servers eingeben, die Konfiguration unseres Clusters erstellen und die Initialisierung durchführen:


docker exec -it mongo mongo
# output omited #
> config = {
    "_id" : "rs0",
    "members" : [
        {
            "_id" : 0,
            "host" : "mongo:27017"
        }
    ]
}
> rs.initiate(config)
{
    "ok" : 1,
    "operationTime" : Timestamp(1531248932, 1),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1531248932, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
}
rs0:SECONDARY>
rs0:PRIMARY>

Fertig Wir haben einen Cluster von einem MongoDB-Server. Jetzt können Sie überprüfen, ob alles wie erwartet funktioniert.


rs0:PRIMARY> session = db.getMongo().startSession()
session { "id" : UUID("7eb81006-983f-4398-adc7-5ed23e027377") }
rs0:PRIMARY> database = session.getDatabase("test")
test
rs0:PRIMARY> // Создадим несколько документов
rs0:PRIMARY> database.col.insert({name: "1"})
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> database.col.insert({name: "2"})
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> database.col.insert({name: "3"})
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> database.col.insert({name: "4"})
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> // Посмотрим, что у нас получилось
rs0:PRIMARY> database.col.find({})
{ "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "1" }
{ "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" }
{ "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" }
{ "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "4" }
rs0:PRIMARY> // Начинаем транзакцию
rs0:PRIMARY> session.startTransaction()
rs0:PRIMARY> // Изменим один документ
rs0:PRIMARY> database.col.update({name: "4"}, {name: "44"})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
rs0:PRIMARY> // Проверим изменения
rs0:PRIMARY> database.col.find({})
{ "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "1" }
{ "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" }
{ "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" }
{ "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "44" }
rs0:PRIMARY> // Можно открыть соседний терминал и убедиться в другой сесии, что документ выглядит по-прежнему:
rs0:PRIMARY> // { "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "4" }
rs0:PRIMARY> // Сохраняем изменения
rs0:PRIMARY> session.commitTransaction()
rs0:PRIMARY> // Проверяем результат
rs0:PRIMARY> database.col.find({})
{ "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "1" }
{ "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" }
{ "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" }
{ "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "44" }
rs0:PRIMARY> // Попробуем изменить несколько документов
rs0:PRIMARY> session.startTransaction()
rs0:PRIMARY> database.col.update({name: "44"}, {name: "42"})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
rs0:PRIMARY> database.col.find({})
{ "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "1" }
{ "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" }
{ "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" }
{ "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "42" }
rs0:PRIMARY> database.col.update({name: "1"}, {name: "21"})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
rs0:PRIMARY> database.col.find({})
{ "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "21" }
{ "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" }
{ "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" }
{ "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "42" }
rs0:PRIMARY> session.commitTransaction()
rs0:PRIMARY> // Проверяем результат
rs0:PRIMARY> database.col.find({})
{ "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "21" }
{ "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" }
{ "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" }
{ "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "42" }
rs0:PRIMARY> // А теперь убедимся, что работает отмена изменений
rs0:PRIMARY> session.startTransaction()
rs0:PRIMARY> database.col.update({name: "21"}, {name: "1"})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
rs0:PRIMARY> database.col.find({})
{ "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "1" }
{ "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" }
{ "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" }
{ "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "42" }
rs0:PRIMARY> // Отменим изменения
rs0:PRIMARY> session.abortTransaction()
rs0:PRIMARY> database.col.find({})
{ "_id" : ObjectId("5b45026edc396f534f11952f"), "name" : "21" }
{ "_id" : ObjectId("5b450272dc396f534f119530"), "name" : "2" }
{ "_id" : ObjectId("5b450274dc396f534f119531"), "name" : "3" }
{ "_id" : ObjectId("5b450276dc396f534f119532"), "name" : "42" }
rs0:PRIMARY> // Отлично! Данные вернулись в прежнее состояние!
rs0:PRIMARY>

Daher können Sie Mongov-Transaktionen jetzt ohne Anstrengung ausprobieren, ohne einen Multi-Server-Cluster zu starten. Ich rate Ihnen, in die Dokumentation zu schauen und sich über die Einschränkungen von Transaktionen zu informieren . Beispiel: Die Transaktionen "leben" nicht länger als 1 Minute. Wenn Sie keine Zeit zum Speichern der Änderungen haben, werden sie abgebrochen.


PS: Der Zweck dieses Artikels ist nicht, zu lernen, wie man einen Docker benutzt oder mit Monga arbeitet, sondern nur eine schnelle Möglichkeit, neue Werkzeuge dieses interessanten DBMS auszuprobieren.