транзакции MongoDB
Введение в бизнес
В MongoDB операции с одним документом атомарны. Эта атомарность одного документа устраняет необходимость в нескольких документах, поскольку вы можете использовать встроенные документы и массивы для захвата отношений между данными в рамках одной структуры документа, а не для нормализации нескольких документов и коллекций. Транзакции варианта использования.
MongoDB поддерживает транзакции с несколькими документами для случаев, когда чтение и запись в несколько документов (в одной или нескольких коллекциях) должны быть атомарными. Для распределенных транзакций транзакции могут использоваться для нескольких операций, коллекций, баз данных, документов и сегментов.
Транзакции и атомарность
Распределенные транзакции и многодокументные транзакции Начиная с MongoDB 4.2, эти два термина являются синонимами. Распределенные транзакции относятся к транзакциям с несколькими документами в сегментированных кластерах и наборах реплик. Транзакции с несколькими документами (будь то в сегментированных кластерах или наборах реплик) также известны как распределенные транзакции, начиная с MongoDB 4.2. MongoDB поддерживает многодокументные транзакции для ситуаций, когда атомарные операции чтения и записи требуются для нескольких документов (в одной или нескольких коллекциях):
В версии 4.0 MongoDB поддерживает многодокументные транзакции в наборах реплик.
В версии 4.2 MongoDB представила распределенные транзакции, которые добавили поддержку транзакций с несколькими документами в сегментированных кластерах и объединили с существующей поддержкой транзакций с несколькими документами в наборах реплик.
Чтобы использовать транзакции в развертываниях MongoDB 4.2 (наборы реплик и сегментированные кластеры), клиенты должны использовать драйвер MongoDB, обновленный для MongoDB 4.2.
Транзакции с несколькими документами являются атомарными (т. е. предоставляют предложение «ничего»):
Когда транзакция фиксируется, все изменения данных, сделанные в транзакции, сохраняются и видны вне транзакции. То есть транзакция не фиксирует одни свои изменения и откатывает другие.
Изменения данных, внесенные в транзакцию, не видны за пределами транзакции, пока транзакция не будет зафиксирована.
Однако, когда транзакция записывает в несколько сегментов, не все внешние операции чтения должны ждать, пока результат зафиксированной транзакции станет видимым в сегментах. Например, если транзакция зафиксирована, запись 1 видна на осколке А, но запись 2 еще не видна на осколке Б, внешнее чтение «локальное» при чтении может прочитать результат записи 1, при этом видя меньше, чем запись 2. .
Когда транзакция прерывается, все изменения данных, сделанные в рамках транзакции, отбрасываются, не становясь видимыми. Например, если какая-либо операция в транзакции завершается сбоем, транзакция прерывается, и все изменения данных, сделанные в транзакции, отбрасываются, не становясь видимыми.
Готов к работе
Предпосылка MongoDB с использованием транзакций заключается в том, что версия MongoDB выше 4.0, а рабочий режим MongoDB необходимо настроить как набор реплик.Одного узла MongoDB недостаточно для поддержки транзакций, поскольку для транзакций MongoDB требуется как минимум два узла. Один из них — главный узел, отвечающий за обработку клиентских запросов, а остальные — подчиненные узлы, отвечающие за репликацию данных на главном узле. Общие методы коллокации узлов mongodb: один главный и один подчиненный, один главный и несколько подчиненных. Главный узел записывает на нем все оплоги операций, а подчиненный узел периодически опрашивает главный узел, чтобы получить эти операции, а затем выполняет эти операции над своей собственной копией данных, тем самым обеспечивая согласованность данных подчиненного узла с данными главного узла.
развертывать | Версия совместимости функций |
---|---|
набор реплик | 4.0 |
Разделенный кластер | 4.2 |
развертывание командной строки
Запустить экземпляр
mongod --replSet rs --dbpath=磁盘目录 --port=27017
mongod --replSet rs --dbpath=磁盘目录 --port=37017
Mongo shell
$mongo --port=27017
MongoDB shell version v4.2.3 connecting to: mongodb://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 ...
конфигурация набора реплик
var config={ _id:"rs", members:[ {_id:0,host:"127.0.0.1:27017"}, {_id:1,host:"127.0.0.1:37017"}, ]}; rs.initiate(config) // 成功后会返回类似如下信息 { "ok" : 1, "operationTime" : Timestamp(1522810920, 1), "$clusterTime" : { "clusterTime" : Timestamp(1522810920, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
контейнер
Развертывание докера
//指定 MongoDB 版本 > 4.0,也可以指定latest
docker pull mongo:4.2.3
Запустите контейнер Docker
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"
mongo shell
// 先进入 Docker 容器交互模式
docker exec -it CONTAINERID /bin/bash
剩余配置方法与命令行部署相同
Использование транзакций MongoDB в Node.js
Использование драйвера MongoDB
// 对于副本集来说 Uri 中需要包含副本集名称,和成员 URI // const uri = 'mongodb://mongodb0.example.com:27017,mongodb1.example.com:27017/?replicaSet=myRepl' // 对于分片集群,连接到mongo集群实例 // 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' });
// 第一步 启动 session,事务的所有操作都基于 session const session = client.startSession();
// 第二步 定义事务选项 const transactionOptions = { readPreference: 'primary', readConcern: { level: 'local' }, writeConcern: { w: 'majority' } };
// 第三步:使用withTransaction启动事务、执行回调和提交
try { await session.withTransaction(async () => { const coll1 = client.db('mydb1').collection('foo'); const coll2 = client.db('mydb2').collection('bar');
// 必须将会话传递给操作 await coll1.insertOne({ abc: 1 }, { session }); await coll2.insertOne({ xyz: 999 }, { session }); }, transactionOptions);
} finally { await session.endSession(); await client.close(); }
скопировать код
Использование mongoose для выполнения транзакций в среде Egg.js
Вопросы, требующие внимания См. (статья блогера блог-сада)
- Вам нужно использовать mongoose.connection для выполнения транзакционных операций над коллекцией.Методы CRUD других моделей не поддерживают транзакции
mongoose.connection.collection('集合名') // 注:集合名需要小写且加s,如model为Cat,集合名这里应写为cats
- Для запуска определяемых схемой значений промежуточного программного обеспечения по умолчанию требуется создание экземпляров модели.
const CatSchema = new Schema({ name: { type: String default: 'cat' }, created: { type: Date, default: Date.now } })
const Cat = mongoose.model('Cat', CatSchema)
new Cat() // 触发中间件
- InsertOne, findOneAndUpdate и другие методы для добавления данных должны полагаться на второй пункт выше, в противном случае insertOne напрямую вставляет часть данных, определенное значение по умолчанию не будет активировано, например, созданное поле, соответствующее поле типа: Схема .ObjectId, определенный в chema, insertOne станет строковым типом после вставки, а не типом Schema.ObjectId
// 解决方式 //新增
const Cat= new Cat(); const data = {name: 5} for (let key in data) { Cat[key] = data[key]; } db.collection('cats').insertOne(Cat);
// 查询修改
db.collection('cats') .findOneAndUpdate({_id: mongoose.Types.ObjectId(你的id)}, {$set: {name: 修改值}})
расширить контекст
// /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; }, };
Модель
'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(); // 触发中间件 return Cat; };
выполнить транзакцию
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 session.commitTransaction();
return 'ok';
} catch (err) {
// 回滚事务
const res = await session.abortTransaction();
console.log(res)
this.ctx.logger.error(new Error(err));
} finally {
await session.endSession();
}
// 执行后,数据库中多了一条 { name: 'ceshi'} 的记录
откат транзакции
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 });
// 手动抛出异常
await this.ctx.throw();
// 提交事务
await session.commitTransaction();
return 'ok';
} catch (err) {
// 回滚事务
await session.abortTransaction();
this.ctx.logger.error(new Error(err));
} finally {
// 结束事务
await session.endSession();
}
// 手动抛出异常后,事务回滚,查看数据库可以看到,插入和删除文档都没有生效
В этой статье используетсяmdniceнабор текста