副问题[/!--empirenews.page--]
媒介
信托行使过主流的相关型数据库的伴侣对“事宜(Transactions)”不会太生疏,它可以让我们把对多张表的多次数据库操纵整合为一次原子操纵,这在高并发场景下可以担保多个数据操纵之间的互不滋扰;而且一旦在这些操纵进程任一环节中呈现了错误,事宜会中止而且让数据回滚,这使得同时在多张表中修改数据的时辰担保了数据的同等性。
早年 MongoDB 是不支持事宜的,因此开拓者在必要用到事宜的时辰,不得不借用其他器材,在营业代码层面去补充数据库的不敷。跟着 4.0 版本的宣布,MongoDB 也为我们带来了原生的事宜操纵,下面就让我们一路来熟悉它,并通过简朴的例子相识怎样去行使。
先容
事宜和副本集(Replica Sets)
副本集是 MongoDB 的一种主副节点架构,它使数据获得最大的可用性,停止单点妨碍引起的整个处事不能会见的环境的产生。今朝 MongoDB 的多表事宜操纵仅支持在副本集上运行,想要在当地情形安装运行副本集可以借助一个器材包——run-rs,以下的文章中有具体的行使声名:
https://thecodebarbarian.com/...
事宜和会话(Sessions)
事宜和会话(Sessions)关联,一个会话统一时候只能开启一个事宜操纵,当一个会话断开,这个会话中的事宜也会竣事。
事宜中的函数
- Session.startTransaction()
在当前会话中开始一次事宜,事宜开启后就可以开始举办数据操纵。在事宜中执行的数据操纵是对外断绝的,也就是说事宜中的操纵是原子性的。
- Session.commitTransaction()
提交事宜,将事宜中对数据的修改造行生涯,然后竣事当前事宜,一次事宜在提交之前的数据操纵对外都是不行见的。
- Session.abortTransaction()
中止当前的事宜,并将事宜中执行过的数据修改回滚。
重试
当事宜运行中报错,catch 到的错误工具中会包括一个属性名为 errorLabels 的数组,当这个数组中包括以下2个元素的时辰,代表我们可以从头提倡响应的事宜操纵。
- TransientTransactionError:呈此刻事宜开启以及随后的数据操纵阶段
- UnknownTransactionCommitResult:呈此刻提交事宜阶段
示例
颠末上面的铺垫,你是不是已经火烧眉毛想知道毕竟应该怎么写代码去完成一次完备的事宜操纵?下面我们就简朴写一个例子:
场景描写: 假设一个买卖营业体系中有2张表——记录商品的名称、库存数目等信息的表 commodities,和记录订单的表 orders。当用户下单的时辰,起主要找到 commodities 表中对应的商品,判定库存数目是否满意该笔订单的需求,是的话则减去响应的值,然后在 orders 表中插入一条订单数据。在高并发场景下,也许在查询库存数目和镌汰库存的进程中,又收到了一次新的建设订单哀求,这个时辰也许就会出题目,由于新的哀求在查询库存的时辰,上一次操纵还未完成镌汰库存的操纵,这个时辰查询到的库存数目也许是富裕的,于是开始执行后续的操纵,现实上也许上一次操纵镌汰了库存后,库存的数目就已经不敷了,于是新的下单哀求也许就会导致现实建设的订单数目高出库存数目。
以往要办理这个题目,我们可以用给商品数据“加锁”的方法,好比基于 Redis 的各类锁,统一时候只应承一个订单操纵一个商品数据,这种方案能办理题目,弱点就是代码更伟大了,而且机能会较量低。假如用数据库事宜的方法就可以简捷许多:
commodities 表数据(stock 为库存):
- { "_id" : ObjectId("5af0776263426f87dd69319a"), "name" : "灭霸原味手套", "stock" : 5 }
- { "_id" : ObjectId("5af0776263426f87dd693198"), "name" : "雷神专用铁锤", "stock" : 2 }
orders 表数据:
- { "_id" : ObjectId("5af07daa051d92f02462644c"), "commodity": ObjectId("5af0776263426f87dd69319a"), "amount": 2 }
- { "_id" : ObjectId("5af07daa051d92f02462644b"), "commodity": ObjectId("5af0776263426f87dd693198"), "amount": 3 }
通过一次事宜完成建设订单操纵(mongo Shell):
- // 执行 txnFunc 而且在碰着 TransientTransactionError 的时辰重试
- function runTransactionWithRetry(txnFunc, session) {
- while (true) {
- try {
- txnFunc(session); // 执行事宜
- break;
- } catch (error) {
- if (
- error.hasOwnProperty('errorLabels') &&
- error.errorLabels.includes('TransientTransactionError')
- ) {
- print('TransientTransactionError, retrying transaction ...');
- continue;
- } else {
- throw error;
- }
- }
- }
- }
-
- // 提交事宜而且在碰着 UnknownTransactionCommitResult 的时辰重试
- function commitWithRetry(session) {
- while (true) {
- try {
- session.commitTransaction();
- print('Transaction committed.');
- break;
- } catch (error) {
- if (
- error.hasOwnProperty('errorLabels') &&
- error.errorLabels.includes('UnknownTransactionCommitResult')
- ) {
- print('UnknownTransactionCommitResult, retrying commit operation ...');
- continue;
- } else {
- print('Error during commit ...');
- throw error;
- }
- }
- }
- }
-
- // 在一次事宜中完成建设订单操纵
- function createOrder(session) {
- var commoditiesCollection = session.getDatabase('mall').commodities;
- var ordersCollection = session.getDatabase('mall').orders;
- // 假设该笔订单中商品的数目
- var orderAmount = 3;
- // 假设商品的ID
- var commodityID = ObjectId('5af0776263426f87dd69319a');
-
- session.startTransaction({
- readConcern: { level: 'snapshot' },
- writeConcern: { w: 'majority' },
- });
-
- try {
- var { stock } = commoditiesCollection.findOne({ _id: commodityID });
- if (stock < orderAmount) {
- print('Stock is not enough');
- session.abortTransaction();
- throw new Error('Stock is not enough');
- }
- commoditiesCollection.updateOne(
- { _id: commodityID },
- { $inc: { stock: -orderAmount } }
- );
- ordersCollection.insertOne({
- commodity: commodityID,
- amount: orderAmount,
- });
- } catch (error) {
- print('Caught exception during transaction, aborting.');
- session.abortTransaction();
- throw error;
- }
-
- commitWithRetry(session);
- }
-
- // 提倡一次会话
- var session = db.getMongo().startSession({ readPreference: { mode: 'primary' } });
-
- try {
- runTransactionWithRetry(createOrder, session);
- } catch (error) {
- // 错误处理赏罚
- } finally {
- session.endSession();
- }
(编辑:河北网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|