mysql mongodb 事务_认识MongoDB 4.0的新特性——事务(Transactions)
前言
相信使用过主流的关系型数据库的朋友对“事务(Transactions)”不会太陌生,它可以让我们把对多张表的多次数据库操作整合为一次原子操作,这在高并发场景下可以保证多个数据操作之间的互不干扰;并且一旦在这些操作过程任一环节中出现了错误,事务会中止并且让数据回滚,这使得同时在多张表中修改数据的时候保证了数据的一致性。
以前 MongoDB 是不支持事务的,因此开发者在需要用到事务的时候,不得不借用其他工具,在业务代码层面去弥补数据库的不足。随着 4.0 版本的发布,MongoDB 也为我们带来了原生的事务操作,下面就让我们一起来认识它,并通过简单的例子了解如何去使用。
介绍
事务和副本集(Replica Sets)
副本集是 MongoDB 的一种主副节点架构,它使数据得到***的可用性,避免单点故障引起的整个服务不能访问的情况的发生。目前 MongoDB 的多表事务操作仅支持在副本集上运行,想要在本地环境安装运行副本集可以借助一个工具包——run-rs,以下的文章中有详细的使用说明:
事务和会话(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 的时候重试
functionrunTransactionWithRetry(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 的时候重试
functioncommitWithRetry(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;
}
}
}
}
// 在一次事务中完成创建订单操作
functioncreateOrder(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
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();
}
上面的代码看着感觉很多,其实 runTransactionWithRetry 和 commitWithRetry 这两个函数都是可以抽离出来成为公共函数的,不需要每次操作都重复书写。用上了事务之后,因为事务中的数据操作都是一次原子操作,所以我们就不需要考虑分布并发导致的数据一致性的问题,是不是感觉简单了许多?
你可能注意到了,代码中在执行 startTransaction 的时候设置了两个参数——readConcern 和 writeConcern,这是 MongoDB 读写操作的确认级别,在这里用于在副本集中平衡数据读写操作的可靠性和性能,如果在这里展开就太多了,所以感兴趣的朋友建议去阅读官方文档了解一下:
readConcern:
writeConcern:
【编辑推荐】
【责任编辑:庞桂玉 TEL:(010)68476606】
点赞 0
mysql mongodb 事务_认识MongoDB 4.0的新特性——事务(Transactions)相关推荐
- mysql 5.0 php_PHP 5.0的新特性
PHP 5.0的新特性 最近,读者可以从PHP 4.x版本转移到PHP 5.0版本.正如读者期望的那样,在一个新的主要版本中,它做出了一些重要变更.在这个版本中,PHP后台的Zend引擎经过了完全的重 ...
- 高性能数据库引擎 CoolHash 产品宣言 Fourinone4.0版新特性(转)
Fourinone4.0版新特性:一个高性能的数据库引擎CoolHash(酷哈嘻) 一.前言:如何写一个数据库 如果将操作系统和业务应用之间的软件都统称中间件的话,那么最重要的软件无疑是数据库,它比w ...
- 盘点Greenplum 6.0六大新特性及展望
导读:本文介绍Greenplum 6.0的新特性. 作者:王春波 来源:大数据DT(ID:hzdashuju) Greenplum 6.0于2019年9月4日正式发布,内核版本从PostgreSQL ...
- C# 8.0 的新特性概览和讲解
本文转自 https://blog.csdn.net/hez2010/article/details/84036742 C# 8.0 的新特性概览和讲解 前言 新的改变 可空引用类型(Nullable ...
- PHP7.0 的新特性
PHP7.0 的新特性经常会被面试官问到,在平时的项目开发中运用它们也会提升一定的代码质量和代码运行性能,可见这个知识点的重要性.不过有很多人都没有去系统的了解掌握,今天我刚好系统整理了一番分享出来. ...
- .NET 4.0 Interop新特性ICustomQueryInterface (转载)
.NET 4.0 Interop新特性ICustomQueryInterface 在.NET Framework v4.0发布的新功能中,在名字空间System.Runtime.InteropServ ...
- .NET Framework 4.0的新特性
本文将揭示.NET 4.0中的3个新特性:图表控件.SEO支持以及ASP.NET 4可扩展的输出缓存. 图表控件 微软向开发者提供了大量可免费下载的图表控件,可以在.NET 3.5 ASP.NET或W ...
- Tensorflow 2.0的新特性
Tensorflow 2.0的新特性 几天前,Tensorflow刚度过自己的3岁生日,作为当前最受欢迎的机器学习框架,Tensorflow在这个宝座上已经盘踞了近三年.无论是成熟的Keras,还是风 ...
- java 7.0 特性_JDK7.0语法新特性
JDK7.0语法新特性 1,菱形语法(泛型实例化类型自动推断) List list = new ArrayList<>(); // <>这个真的很像菱形 2,在目前版本中,不可 ...
最新文章
- python根据二叉树的前序遍历和中序遍结果历重建二叉树
- php伪协议实现命令执行的七种姿势
- 传统POS/终端/银联POS简介
- 【BZOJ 1597】 [Usaco2008 Mar]土地购买 (斜率优化)
- Ubuntu17.04 之 systemd 设置开机启动
- cx_Oracle怎么打包,cx_Oracle 在执行包文件 function 时有多个出参该如何获取
- 数学建模1(历年问题与模型)
- 阿里、腾讯隔空“对话”互联互通 打破垄断让中小商户受益是核心命题
- Windows server 2012 hyper-v 的实时迁移
- html5用户注册页面代码,HTML5注册页面示例代码_html5教程技巧
- MATLAB实现的Reed-Muller(RM码,里德-马勒编码)编码解码纠错以及BER分析
- 排队论(Queuing Theory)
- 记录mikrotik路由器的折腾过程
- 计算机word虚线在哪里,电脑虚线怎么打出来
- 备战数学建模23-数据包络分析
- 中国浓缩咖啡机行业市场供需与战略研究报告
- MongoDB 4.2.3 安装以及安装遇到的问题“service MongoDB failed to start,verify that you have sufficient privilege”
- Pycharm自动添加文件头注释和函数注释参数
- 12306登录验证码识别(Java版)
- FFmpeg —— librtmp的安装(linux)