当前位置: 首页 > 科技观察

认识一下MongoDB4.0的新特性——事务

时间:2023-03-22 11:59:55 科技观察

前言相信用过主流关系型数据库的朋友对“事务”都不会太陌生,它可以让我们将多个表的数据库操作整合为一个原子操作,可以保证高并发场景下多个数据操作互不干扰;一旦这些操作的任何部分发生错误,事务将被暂停,数据将被回滚。这样可以保证同时修改多个表中的数据时的数据一致性。以前MongoDB是不支持事务的,所以当开发者需要使用事务时,只能借用其他工具来弥补数据库在业务代码层面的不足。随着4.0版本的发布,MongoDB也为我们带来了原生的事务操作。让我们一起来了解它,并通过简单的例子来学习如何使用它。事务和副本集(ReplicaSets)简介副本集是MongoDB的一种主从节点架构,能够使数据完全可用,避免单点故障导致整个服务无法访问。目前MongoDB的多表事务操作只支持在副本集上运行。如果你想在本地环境中安装和运行副本集,你可以使用一个工具包——run-rs。以下文章有详细说明:https://thecodebarbarian.com/...Transactions与Session相关联。事务与会话相关联。一个会话一次只能开启一个事务操作。当一个会话断开时,这个会话中的事务也将结束。事务中的函数Session.startTransaction()在当前会话中启动一个事务,事务打开后即可开始数据操作。事务中进行的数据操作是与外界隔离的,也就是说事务中的操作是原子的。Session.commitTransaction()提交事务,将对数据的修改保存在事务中,然后结束当前事务。事务在提交前的数据操作是外界不可见的。Session.abortTransaction()中止当前事务并回滚事务中执行的数据修改。重试当事务运行报错时,捕捉到的错误对象会包含一个数组,该数组的属性名为errorLabels。当这个数组包含以下2个元素时,就意味着我们可以重新发起相应的交易操作。TransientTransactionError:出现在事务开启及后续数据操作阶段UnknownTransactionCommitResult:出现在commit事务阶段Example经过上面的铺垫,是不是迫不及待想知道如何编写代码来完成一次完整的事务操作了?下面简单写一个例子:场景描述:假设一个交易系统中有2张表——记录商品名称、库存数量等信息的表commodities,记录订单的表orders。用户下单时,首先要在商品表中找到对应的商品,判断库存数量是否满足订单的需求。如果有,则减去相应的值,然后在orders表中插入一条订单数据。在高并发场景下,在查询库存数量和减少库存的过程中,可能会收到新的订单创建请求。这时候可能会出现问题,因为新的请求在查询库存的时候,上一个操作还没有完成减库存操作。此时查询到的库存数量可能是充足的,所以开始后续操作。实际上可能是上次操作减库存后库存数量不足,重新下单。请求可能会导致创建的订单数量多于库存数量。以往解决这个问题,我们可以采用商品数据“加锁”的方式,比如基于Redis的各种锁,一次只允许一个订单操作一个商品数据。这种方案可以解决问题,缺点是代码比较复杂,性能会比较低。如果使用数据库事务方式,可以简单很多:Commodities表数据(stock即库存):{"_id":ObjectId("5af0776263426f87dd69319a"),"name":"ThanosOriginalGloves","stock":5}{"_id":ObjectId("5af0776263426f87dd693198"),"name":"雷神之锤","stock":2}订单表数据:{"_id":ObjectId("5af07daa051d92f02462644c"),"commodity":ObjectId("5af0776263426f87dd69319a"),"amount":2}{"_id":ObjectId("5af07daa051d92f02462644b"),"commodity":ObjectId("5af0776263426f87dd693198"),"amount":3}通过一笔交易完成订单的创建(mongoShell)://执行txnFunc,遇到TransientTransactionError时重试('errorLabels')&&error.errorLabels.includes('TransientTransactionError')){print('TransientTransactionError,retryingtransaction...');continue;}else{throwerror;}}}}//提交事务,遇到UnknownTransactionCommitResult时重试){if(error.hasOwnProperty('errorLabels')&&error.errorLabels.includes('UnknownTransactionCommitResult')){print('UnknownTransactionCommitResult,retryingcommitoperation...');continue;}else{print('Errorduringcommit...');throwerror;}}}}//在一个事务中创建一个订单quantityofgoodsinthisordervarorderAmount=3;//假设商品的IDvarcommodityID=ObjectId('5af0776263426f87dd69319a');session.startTransaction({readConcern:{level:'snapshot'},writeConcern:{w:'majority'},});try{var{stock}=commoditiesCollection.findOne({_id:commodityID});if(stock