Tranzacții cu sesiuni (startTransaction) în MongoDB & NoSQL

Tranzacțiile grupează mai multe scrieri, pe mai multe colecții, astfel încât să se aplice toate sau niciuna; cer replica set sau cluster.

Sintaxă
const session = db.getMongo().startSession()
session.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' } })
session.commitTransaction() | session.abortTransaction()
session.endSession()

Ce face Tranzacții cu sesiuni (startTransaction)

Scrierile pe un singur document sunt atomice și fără tranzacții. Când o operație atinge mai multe documente sau colecții (scazi stocul ȘI creezi comanda), o tranzacție garantează că fie se aplică toate, fie niciuna. Tranzacțiile multi-document există din MongoDB 4.0 pe replica set și din 4.2 pe clustere sharded; pe un server de sine stătător nu funcționează.

În mongosh pornești o sesiune, iei colecțiile prin session.getDatabase(...), apoi startTransaction(), scrierile și commitTransaction(). Până la commit, celelalte conexiuni nu văd modificările. abortTransaction() le anulează. O tranzacție trebuie să fie scurtă: implicit este anulată după 60 de secunde (transactionLifetimeLimitSeconds).

Parametri

Parametru Descriere
startSession() Creează sesiunea în care rulează tranzacția.
readConcern / writeConcern Nivelurile de consistență; snapshot și majority sunt alegerea obișnuită.
commitTransaction() Aplică toate modificările.
abortTransaction() Anulează toate modificările.
withTransaction(fn) Rulează funcția într-o tranzacție și o reîncearcă automat la erori temporare.

Ce returnează

startTransaction(), commitTransaction() și abortTransaction() nu afișează nimic dacă reușesc; scrierile din interior întorc rezultatele obișnuite.

Exemple

Exemplele de pe această pagină pornesc de la datele de mai jos. Rulează mai întâi acest cod, apoi exemplul.

use magazin
db.produse.insertOne({ _id: 'caiet', stoc: 40, pret: 12 })

Exemplul 1: Comandă și scăderea stocului, împreună

Dacă oricare pas ar eșua, am apela abortTransaction() și nimic nu s-ar salva.

const session = db.getMongo().startSession()
const s = session.getDatabase('magazin')
session.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' } })
s.produse.updateOne({ _id: 'caiet', stoc: { $gte: 2 } }, { $inc: { stoc: -2 } })
s.comenzi.insertOne({ _id: 7, client: 'Ana', produse: ['caiet', 'caiet'], total: 24 })
session.commitTransaction()
session.endSession()

Ieșire în mongosh

rs0 [direct: primary] magazin> const session = db.getMongo().startSession()
rs0 [direct: primary] magazin> const s = session.getDatabase('magazin')
rs0 [direct: primary] magazin> session.startTransaction({ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' } })
rs0 [direct: primary] magazin> s.produse.updateOne({ _id: 'caiet', stoc: { $gte: 2 } }, { $inc: { stoc: -2 } })
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
rs0 [direct: primary] magazin> s.comenzi.insertOne({ _id: 7, client: 'Ana', produse: ['caiet', 'caiet'], total: 24 })
{ acknowledged: true, insertedId: 7 }
rs0 [direct: primary] magazin> session.commitTransaction()
rs0 [direct: primary] magazin> session.endSession()

Exemplul 2: Pe un server fără replica set

Tranzacțiile au nevoie de un replica set (pentru dezvoltare ajunge unul cu un singur membru). Pe un server de sine stătător, prima scriere din tranzacție dă eroare.

const session = db.getMongo().startSession()
session.startTransaction()
session.getDatabase('magazin').comenzi.insertOne({ _id: 8, client: 'Dan', total: 10 })

Ieșire în mongosh

magazin> const session = db.getMongo().startSession()
magazin> session.startTransaction()
magazin> session.getDatabase('magazin').comenzi.insertOne({ _id: 8, client: 'Dan', total: 10 })
MongoServerError[IllegalOperation]: Transaction numbers are only allowed on a replica set member or mongos

Greșeli frecvente

  • Operațiile trebuie făcute prin colecțiile obținute din sesiune (session.getDatabase(...)); cele luate direct din db nu fac parte din tranzacție.
  • Nu ține tranzacții deschise în timp ce aștepți utilizatorul sau un serviciu extern: blochează resurse și sunt anulate după 60 de secunde.
  • Multe probleme se rezolvă fără tranzacții, prin modelarea datelor în același document și operații atomice ($inc, findOneAndUpdate).

Comenzi înrudite

  • bulkWrite(): Trimite mai multe operații de scriere (inserări, actualizări, ștergeri) către o colecție într-o singură cerere.
  • findOneAndUpdate(): Actualizează un document și îl întoarce, atomic, într-un singur pas; există și findOneAndDelete și findOneAndReplace.
  • upsert: true și $setOnInsert: Cu upsert: true, updateOne actualizează documentul găsit sau, dacă nu există, îl creează; $setOnInsert scrie câmpuri doar la creare.