MongoDB имеет атомарность в операциях с одним документом, но больше не имеет этой функции в операциях с несколькими документами, обычно требующих помощи транзакций для достижения свойств ACID.
Знакомство с API транзакций
Операции клиента с транзакциями реализуются драйвером клиента MongoDB для предоставления соответствующего интерфейса API. Транзакции поддерживаются только после MongoDB 4.0, и для версии клиентского драйвера также должна быть выбрана соответствующая версия.
Этот документ принимаетДрайвер клиента MongoDB версии 3.5
Сессия Сессия
Сессия — это концепция, появившаяся после MongoDB 3.6.В предыдущих версиях каждый запрос в процессе Mongod создавал контекст (OperationContext), который можно понимать как однострочную транзакцию.Изменения данных, индексов и оплогов в этой однострочной транзакции являются атомарными..
Сеанс после MongoDB 3.6 также по сути является контекстом.В этом сеансе сеанса несколько запросов имеют общий контекст, который обеспечивает основу для реализации многодокументной транзакции.
Точка знаний:Почему db.coll.count() часто неточен после сбоя?
причина в томОбновление количества записей таблицы не зависит от транзакции обновления данных., справочная статьяМ.current.com/archives/54….
функция транзакции
- startTransaction()
Начать новую транзакцию, после чего можно выполнять CRUD-операции.
- commitTransaction()
Транзакция фиксации сохраняет данные, и измененные данные в транзакции не видны внешнему миру до фиксации.
- abortTransaction()
Транзакция откатывается, например, если часть данных не удается обновить, измененные данные также откатываются.
- endSession()
Завершите этот сеанс.
Простая реализация в Mongo Shell
var session = db.getMongo().startSession();
session.startTransaction({readConcern: { level: 'majority' },writeConcern: { w: 'majority' }});
var coll = session.getDatabase('test').getCollection('user');
coll.update({name: 'Jack'}, {$set: {age: 18}})
// 成功提交事务
session.commitTransaction();
// 失败事务回滚
session.abortTransaction();
Практика транзакций MongoDB в Nodejs
Чтобы лучше понять, как транзакции MongoDB используются в Node.js, приведем пример.
Предположим, теперь у нас есть такой сценарий заказа продуктов в торговом центре, который разделен на одну таблицу продуктов (хранение данных о продуктах, информацию о запасах) и другую таблицу заказов (хранение записей заказов). Перед размещением заказа необходимо проверить, больше ли запасов 0. Если больше 0, запас товара будет вычтен и заказ будет создан, в противном случае будет указано, что запасов недостаточно и заказ не может быть размещен.
модель данных
// goods
{
"_id": ObjectId("5e3b839ec2d95bfeecaad6b8"),
"goodId":"g1000", // 商品 Id
"name":"测试商品1", // 商品名称
"stock":2, // 商品库存
"price":100 // 商品金额
}
// db.goods.insert({ "goodId" : "g1000", "name" : "测试商品1", "stock" : 2, "price" : 100 })
// order_goods
{
"_id":ObjectId("5e3b8401c2d95bfeecaad6b9"),
"id":"o10000", // 订单id
"goodId":"g1000", // 订单对应的商品 Id
"price":100 // 订单金额
}
// db.order_goods.insert({ id: "o10000", goodId: "g1000", price: 100 })
Операция Node.js Реализация собственного API MongoDB
Уведомление: в операции транзакции readPreference должен быть установлен на первичный узел, а не на вторичный узел.
db.js
Подключитесь к MongoDB и инициализируйте экземпляр.
const MongoClient = require('mongodb').MongoClient;
const dbConnectionUrl = 'mongodb://192.168.6.131:27017,192.168.6.131:27018,192.168.6.131:27019/?replicaSet=May&readPreference=secondaryPreferred';
const client = new MongoClient(dbConnectionUrl, {
useUnifiedTopology: true,
});
let instance = null;
module.exports = {
dbInstance: async () => {
if (instance) {
return instance;
}
try {
instance = await client.connect();
} catch(err) {
console.log(`[MongoDB connection] ERROR: ${err}`);
throw err;
}
process.on('exit', () => {
instance.close();
});
return instance;
}
};
index.js
const db = require('./db');
const testTransaction = async (goodId) => {
const client = await db.dbInstance();
const transactionOptions = {
readConcern: { level: 'majority' },
writeConcern: { w: 'majority' },
readPreference: 'primary',
};
const session = client.startSession();
console.log('事务状态:', session.transaction.state);
try {
session.startTransaction(transactionOptions);
console.log('事务状态:', session.transaction.state);
const goodsColl = await client.db('test').collection('goods');
const orderGoodsColl = await client.db('test').collection('order_goods');
const { stock, price } = await goodsColl.findOne({ goodId }, { session });
console.log('事务状态:', session.transaction.state);
if (stock <= 0) {
throw new Error('库存不足');
}
await goodsColl.updateOne({ goodId }, {
$inc: { stock: -1 } // 库存减 1
})
await orderGoodsColl.insertOne({ id: Math.floor(Math.random() * 1000), goodId, price }, { session });
await session.commitTransaction();
} catch(err) {
console.log(`[MongoDB transaction] ERROR: ${err}`);
await session.abortTransaction();
} finally {
await session.endSession();
console.log('事务状态:', session.transaction.state);
}
}
testTransaction('g1000')
запустить тест
После выполнения каждой функции транзакции просмотрите текущий статус транзакции.
node index
事务状态: NO_TRANSACTION
事务状态: STARTING_TRANSACTION
事务状态: TRANSACTION_IN_PROGRESS
事务状态: TRANSACTION_COMMITTED