Directing a transaction

The transaction is introduced

In MongoDB, operations on individual documents are atomic. Because you can use embedded documents and arrays to capture relationships between data in a single document structure, rather than normalizing across multiple documents and collections, this atomicity of a single document eliminates the need for many practical use case transactions for multiple documents.

MongoDB supports multi-document transactions for situations where you need to atomize reads and writes to multiple documents, either in a single document or in multiple collections. For distributed transactions, transactions can be used for multiple operations, collections, databases, documents, and shards.

Transactions and atomicity

Distributed transactions and multi-document transactions are synonyms since MongoDB 4.2. Distributed transactions refer to multi-document transaction records on sharded clusters and replica sets. Multi-document transactions (whether on a sharded cluster or replica set) are also known as distributed transactions starting with MongoDB 4.2. MongoDB supports multi-document transactions for situations where atomization of reads and writes to multiple documents (in a single or multiple collection) is required:

In version 4.0, MongoDB supports multi-document transactions on replica sets.

In version 4.2, MongoDB introduced distributed transactions, which added support for multi-document transactions on sharded clusters and incorporated existing support for multi-document transactions on replica sets.

To use transactions on MongoDB 4.2 deployments (replica sets and sharded clusters), clients must use MongoDB drivers updated for MongoDB 4.2.

Multidocument transactions are atomic (that is, provide the “nothing” proposition) :

When a transaction commits, all data changes made in the transaction are kept outside the transaction and visible. That is, a transaction does not commit some of its changes and rolls back others.

Data changes made in a transaction are not visible outside the transaction until the transaction commits.

However, when a transaction writes to multiple shards, not all external read operations need to wait for the results of the committed transaction to be visible in the shard. For example, if A transaction is committed and write 1 is visible on shard A, but write 2 is not yet visible on Shard B, the external read “local” at read time can read the result of write 1, but not write 2.

When a transaction aborts, all data changes made in the transaction are discarded without becoming visible. For example, if any operation in the transaction fails, the transaction is aborted, and all data changes made in the transaction are discarded without becoming visible.

The preparatory work

The prerequisite for MongoDB to use transactions is that the MongoDB version is greater than 4.0, and the working mode of MongoDB needs to be set to replica set. A single MongoDB node cannot support transactions, because MongoDB transactions require at least two nodes. One of them is the master node, which handles client requests, and the rest are the slave nodes, which copy data from the master node. Common collocation modes of mongodb nodes are as follows: one master, one slave, and one master, many slave. The master node records all operations on its oplog, the slave node periodically polls the master node for these operations, and then performs these operations on its own copy of data to ensure that the slave node data is consistent with the master node.

The deployment of Functional Compatible Version
A copy of the set 4.0
Shard cluster 4.2
Command line deployment
Restart the instance
Mongod --replSet RS -- dbPath = Disk directory --port=27017 Mongod --replSet RS --dbpath= disk directory --port=37017Copy the code
Mongo shell
$mongo --port=27017
Copy the code

Mongo shell version v4.2.3 connecting to: mongo: / / 127.0.0.1:27017 /? compressors=disabled&gssapiServiceName=mongodb Implicit session: session { "id" : UUID(" b0a2609C-6aa1-466A-849F-ba0e9f5e3D3a ")} MongoDB server version: 4.2.3...

Replica set configuration
Var config = {_id: "rs", members: [{_id: 0, host: 127.0.0.1: "27017"}, {_id: 1, host: 127.0.0.1: "37017"},]}. {" OK ": 1, "operationTime" : Timestamp(1522810920, 1), "$clusterTime" : { "clusterTime" : Timestamp(1522810920, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }Copy the code

The container
Docker deployment
// Specify latest Docker pull mongo:4.2.3Copy the code
Start the Docker container
Docker run --name m0 -P 37017:27017 -d mongo:4.2.3 --replSet "rs" docker run --name m0 -P 47017:27017 -d mongo:4.2.3 --replSet "rs" docker run --name M0 -P 57017:27017 -d mongo:4.2.3 --replSet "rs"Copy the code
mongo shell
Docker exec it CONTAINERID /bin/bash The rest configuration methods are the same as those for cli deploymentCopy the code

MongoDB transactions are used in Node.js

Using the MongoDB driver
// For the replica set, the Uri must contain the replica set name and the member Uri // const Uri = mongodb://mongodb0.example.com: 27017, mongodb1.example.com: 27017 /? = myRepl 'replicaSet / / for shard cluster, Connect to the mongo cluster instance / / const uri = "mongodb://mongos0.example.com: 27017, mongos1.example.com: 27017 / '

const client = new MongoClient(uri); await client.connect();

await client .db('mydb1') .collection('foo') .insertOne({ abc: 0 }, { w: 'majority' });

await client .db('mydb2') .collection('bar') .insertOne({ xyz: 0 }, { w: 'majority' });

Const session = client.startSession();

Const transactionOptions = {readPreference: 'primary', readConcern: {level: 'local'}, writeConcern: { w: 'majority' } };

// Step 3: Start the transaction, perform a callback, and commit with withTransaction

try { await session.withTransaction(async () => { const coll1 = client.db('mydb1').collection('foo'); const coll2 = client.db('mydb2').collection('bar');

InsertOne ({ABC: 1}, {session}); await coll2.insertOne({ xyz: 999 }, { session }); }, transactionOptions);Copy the code

} finally {
await session.endSession();
await client.close();
}

Copy the code

Transactions are performed using Mongoose in the egg.js framework
Matters needing attention refer to (blog garden blogger pocket article)
  • Mongoose. connection is used to transact the collection. The CRUD part of other Models does not support transactions
Mongoose. Connection. The collection (' collection name ') / / note: collection name to lower case and s, such as the model for the Cat, the collection name here should be written as catsCopy the code

  • The middleware defaults that trigger the Schema definition require the construction of a Model instance
Const CatSchema = new Schema({name: {type: String default: 'cat'}, created: {type: Date, default: Date. Now}})

const Cat = mongoose.model('Cat', CatSchema)

Copy the code

New Cat() // Triggers middleware

  • InsertOne, findOneAndUpdate and other methods need to rely on the second point above to add data, otherwise directly insertOne inserts a data, defined default value will not trigger, such as created field,chema internal defined type: The corresponding fields in schema. ObjectId and insertOne will be string types instead of schema. ObjectId
// Solution // New

const Cat= new Cat(); const data = {name: 5} for (let key in data) { Cat[key] = data[key]; } db.collection('cats').insertOne(Cat);

// Query the modification

Copy the code

Db.collection ('cats').findoneAndUpdate ({_id: mongoose.types.objectid (your id)}, {$set: {name: change value}})

Extending the context
// /app/extend/context.js module.exports = { async getSession(opt = { readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' }, }) { const { mongoose } = this.app; const session = await mongoose.startSession(opt); await session.startTransaction(); return session; }};Copy the code

model
'use strict';
module.exports = app => {
  const CatSchema = new app.mongoose.Schema({
    name: {
      type: String,
      default: 'cat',
    },
    pass: {
      type: String,
      default: 'cat',
    },
    created: {
      type: Date,
      default: Date.now,
    },
  });

const Cat = app.mongoose.model('Cat', CatSchema);

new Cat(); // Trigger middleware return Cat; };

Copy the code

Perform transactions
const { mongoose } = this.ctx.app; const session = await this.ctx.getSession(); const db = mongoose.connection; try { let data = { name : 'ceshi' }; const Cat = new this.ctx.model.Cat(); for (let key in data) { Cat[key] = data[key] } await db .collection('cats') .insertOne(Cat, { session }); / / to commit the transaction await session.com mitTransaction (); return 'ok'; } catch (err) {// Roll back the transaction const res = await session.abortTransaction(); console.log(res) this.ctx.logger.error(new Error(err)); } finally { await session.endSession(); } {name: 'ceshi'} {name: 'ceshi'}Copy the code
c6171450a3663e7fe9d77919ea7fd0ab.png
Transaction rollback
const { mongoose } = this.ctx.app; const session = await this.ctx.getSession(); const db = mongoose.connection; try { let data = { name : 'ceshi' }; const Cat = new this.ctx.model.Cat(); for (let key in data) { Cat[key] = data[key] } await db .collection('cats') .insertOne(Cat, { session }); await this.ctx.model.Cat.deleteMany({ name: 'ceshi' }, { session }); // Manually throw an exception await this.ctx.throw(); / / to commit the transaction await session.com mitTransaction (); return 'ok'; } catch (err) {// Roll back the transaction await session.abortTransaction(); this.ctx.logger.error(new Error(err)); } finally {// end the transaction await session.endsession (); } // After manually throwing an exception, the transaction is rolled backCopy the code
d31f585a5cd2097cc0bbb3bb9c35f788.png

This article is formatted using MDNICE