MongoDB

MongoDB ist die führende Open-Source, Document Datenbank die für einfache Entwicklung und Skalierung aber auch für Big Data Szenarien entwickelt wurde.

MongoDB Transaction Insights: Wie man für ein UPDATE ... SELECTed

Marc-David Militz
Experte
  • Artikel von Renato Riccio

    • englischer Originalartikel
      https://www.mongodb.com/blog/post/how-to-select--for-update-inside-mongodb-transactions
      Übersetzung mit freundlicher Genehmigung von MongoDB


      Die häufigste Frage, die man als MongoDB-Berater seit Einführung von ACID-Transaktionen mit mehreren Dokumenten in MongoDB 4.0 erhält, lautet: "Wie kann ich sicher sein, dass die Dokumente, die ich in einer Transaktion lese, vor dem Commit nicht durch eine andere Operation geändert werden?" Bevor man diese Frage beantworten kann, sollte man zunächst erklären, wie das LOCK-System in einer MongoDB-Transaktion funktioniert.

      • Schreib-Konflikte, Locks und Transaktionen

        • Wenn wir Schreibvorgänge innerhalb einer Transaktion ausführen, muss die Datenbank eine exklusive Sperre für das Dokument setzen, das wir ändern möchten. Wenn es nicht in der von "maxTransactionLockRequestTimeoutMillis" definierten Zeit erfasst werden kann (Standard 5 ms), wird ein writeConflict-Fehler ausgelöst.
          Betrachten wir die folgenden Beispiele:

          Eine Transaktion (t1) kann einem WriteConflict unterliegen, wenn ein anderer Schreibvorgang dasselbe Dokument (D1) ändert, nachdem die Transaktion gestartet wurde und bevor die Transaktion selbst versucht, es zu ändern. Dies kann unabhängig davon geschehen, ob sich die andere Schreiboperation in einer Transaktion befindet oder nicht. Dies wird im linken Bild als unabhängige Anweisung und im rechten Bild als Transaktion angezeigt (t2).

          Hier ist ein Beispiel eines "writeConflict" -Fehlers aus Sicht der MongoDB-Shell:
          > coll1.update( { _id: 1 },{ $set: { a: 5 } });
          WriteCommandError({
          "errorLabels" : [
          "TransientTransactionError"
          ],
          "operationTime" : Timestamp(1566806753, 1),
          "ok" : 0,
          "errmsg" : "WriteConflict",
          "code" : 112,
          "codeName" : "WriteConflict",
          "$clusterTime" : {
          "clusterTime" : Timestamp(1566806753, 1),
          "signature" : {
          "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
          "keyId" : NumberLong(0)
          }
          }
          })

          Sobald eine Transaktion die Sperre für ein Dokument erhält, müssen Schreibvorgänge für dieses Dokument, die außerhalb der Transaktion stattfinden, warten, bis die ausgeführte Transaktion, die die Sperre hält, festgeschrieben / abgebrochen wird. Wenn ein "writeConflict" zurückgegeben wird, kann der Treiber die Transaktion mit der neuen Transaktionsrückruf-API, die es ermöglicht, bei "TransientTransactionError" (z.B. "writeConflicts") oder "UnknownTransactionCommitResult", eine automatische Wiederholung durchzuführen, sicher wiederholen. Weitere Informationen zur neuen Callback-API finden Sie in Ihrer Treiberdokumentation. https://docs.mongodb.com/manual/core/transactions-in-applications/#drivers-api

          • Die Antwort auf einen Read

            • Da wir jetzt wissen, was bei jeder Ausführung von Schreibvorgängen in einer Transaktion geschieht, können wir uns darauf konzentrieren, was bei der Ausführung von Leseoperationen geschieht. Wenn eine Transaktion gestartet wird, wird garantiert, dass wir von einem konsistenten Zeitpunkt auf Clusterebene lesen ("Snapshot"). Die Daten, die wir in der Transaktion lesen, werden von keiner Schreiboperation außerhalb der Transaktion beeinflusst. Wenn zum Beispiel die Transaktion t1 gestartet wird, wird ein Snapshot der Daten einschließlich des Dokuments D1 erstellt, das gelesen werden soll. In der Zwischenzeit verfügt die Transaktion t2 über einen eigenen Snapshot und löscht das Dokument D1. Die Transaktion t1 kann das Dokument D1 dennoch lesen, da es sich auf seinen Snapshot bezieht und daher von anderen Schreiboperationen für Transaktionen isoliert ist. Praktisch bedeutet dies, dass wir nicht sicher sein können, dass sich das Dokument während der Laufzeit der Transaktion nicht ändert, wenn Dokumente innerhalb einer Transaktion gelesen werden.

              In der relationalen Welt wird dies mit SELECT ... FOR UPDATE gelöst. Mit dieser Anweisung können Zeilen für das Lesen gesperrt werden, als ob sie aktualisiert worden wären. Dadurch wird verhindert, dass andere Vorgänge sie ändern oder löschen, bis die Transaktion endet. Dasselbe Verhalten kann in MongoDB reproduziert werden, indem eine Schreibsperre für das Dokument aktiviert wird, sodass andere Transaktionen, die versuchen, darauf zu schreiben, mit einem "writeConflict" fehlschlagen.
              Aber was sollen wir aktualisieren? Wir können den Wert eines vorhandenen Feldes nicht einfach mit dem aktuellen Wert überschreiben, da MongoDB effizient ist und ein Dokument nicht ändert, wenn durch einen Schreibvorgang der vorhandene Wert nicht geändert wird.

              Betrachten Sie das folgende Beispiel in der Collection, die das Dokument enthält:
              { _id: 1, status: true }

              Und wir führen das folgende Update innerhalb einer Transaktion durch:
              db.foo.update({ _id: 1 }, { $set: { status: true } })

              Da wir das Dokument nicht ändern (der Status war bereits "true"), versucht die Transaktion nicht, eine Sperre dafür zu erlangen.

              • Eine Schreibsperre beim Lesen erhalten

                • Um sicherzustellen, dass wir eine Schreibsperre erhalten, müssen wir sicherstellen, dass wir einen neuen Wert in unser Dokument schreiben. Normalerweise haben wir kein Feld, das wir einfach ändern können, um ein Schreiben zu ermöglichen, aber das flexible Schema von MongoDB erleichtert es uns, ein Attribut festzulegen, das wir ausschließlich verwenden, um unsere Schreibsperre zu erhalten.

                  Die nächste Frage ist, auf welchen Wert wir unser neues Attribut setzen sollen. Es muss ein Wert sein, von dem wir wissen, dass er sich von dem Wert unterscheidet, der möglicherweise bereits auf dem Gebiet vorhanden ist. In MongoDB haben wir bereits einen Datentyp mit den folgenden Eigenschaften: "ObjectId". Da "ObjectId" auf der Grundlage einer Kombination der Unix-Epoche, zufälliger Werte, die auf Maschinen- und Prozessebene eindeutig sind, und eines Zählers generiert wird, ist es äußerst unwahrscheinlich, dass derselbe Wert mehr als einmal generiert wird. Das heißt, wenn wir es aktualisieren, setzt das Update das Feld auf einen anderen Wert als den, der bereits vorhanden ist. Dadurch wird sichergestellt, dass die Aktualisierung immer stattfindet und die Schreibsperre aktiviert wird.

                  Im Code sieht das, in etwa, so aus:
                  var doc = db.foo.findOneAndUpdate({ _id: 1 },    
                  { $set: { myLock: { appName: "myApp", pseudoRandom: ObjectId() } } })

                  Das Schöne an dieser Methode ist, dass wir keinen weiteren Schritt zum Entsperren des Dokuments ausführen müssen, als die Transaktion festzuschreiben oder abzubrechen. Der Wert des Sperrfelds - in unserem Beispiel "myLock" - kann beliebig sein. Es spielt keine Rolle, um welchen Wert es sich handelt, solange durch Ändern des Werts ein Schreibvorgang ausgeführt und die Schreibsperre aktiviert wird. Mit "findOneAndUpdate" können Sie auch die Anzahl der Roundtrips in der Datenbank auf eins reduzieren, da das Dokument gelesen und das Sperrfeld aktualisiert wird.

                  Der Wert des Sperrfeldes muss von keiner anderen Transaktion gelesen werden. Sie können jedoch das Sperrfeld verwenden, um Informationen darüber zu speichern, welche Anwendung den Thread sperrt. In unserem Beispiel ist "myLock" ein Objekt mit dem Namen der Anwendung, die die Sperre und das Sperrfeld übernimmt.

                  • Zusammenfassung

                    • Die Möglichkeit, Dokumente über die Transaktionsunterstützung von MongoDB zu sperren, ist ein wichtiges Hilfsmittel zur Gewährleistung der Konsistenz. Denken Sie daran, dass MongoDB zwar Transaktionen unterstützt, alle bekannten Best Practices jedoch weiterhin gültig sind. Sie sollten Transaktionen nur bei Bedarf verwenden. Sie sollten immer versuchen, das umfangreiche und flexible MongoDB-Dokumentformat zu nutzen, das in vielen Fällen die Verwendung von Transaktionen überflüssig macht.

                      Die verwendeten Grafiken sind Eigentum von MongoDB.

Neueste Mitgliederaktivitäten

Tags

Diesen Community Beitrag weiterempfehlen