Mongoose中session.abortTransaction无法回滚租赁REST API事务改动且无报错
Mongoose事务回滚失效问题修正
问题描述
开发租赁类REST API时,通过Mongoose事务保障电影库存扣减、客户累计租赁数更新的一致性,遇到如下异常:
- 触发回滚逻辑时
session.abortTransaction执行无报错,但数据库已产生的改动无法被回滚 - 全程没有抛出明确的事务相关错误,仅表现为改动未被正常撤回
原实现代码如下:
const session = await startSession(); const { error } = validateRental(req.body); if (error?.details[0].message) return res.status(400).send(error.details[0].message); const movie = await Movies.findById(req.body.movieID, null, { $session: session, }); const customer = await Customer.findById(req.body.customerID, null, { $session: session, }); if (!movie || !customer) return res.status(400).send("Please check the customer or Movie ID"); try { session.startTransaction(); movie.numberOfStock--; customer.numberOfRental++; await movie.save(); await customer.save(); await session.commitTransaction(); } catch (e) { console.log(e); await session.abortTransaction(); res.status(500).send("Internal System error"); } finally { session.endSession(); }
失效根因
- 写入操作完全脱离事务上下文:代码中
movie.save()、customer.save()调用时未传入关联会话的配置,这两个写操作是独立于事务的普通数据库请求,不受事务的提交/回滚逻辑管控。无论事务最终是正常提交还是异常中止,这两个操作的结果都会直接持久化到数据库,自然无法被回滚。 - 提前返回分支存在会话泄漏:参数校验失败、电影/客户查询为空的分支直接返回响应,没有主动释放会话资源,长期运行会耗尽MongoDB连接池配额。
- Mongoose的会话绑定逻辑是显式的:仅在查询阶段绑定会话,后续的文档修改、保存操作不会自动关联之前使用的会话,必须在每个需要纳入事务管控的操作上显式绑定同一会话。
修正后的可运行实现
const session = await startSession(); try { // 先做参数校验,减少无意义的会话资源占用 const { error } = validateRental(req.body); if (error?.details[0].message) { return res.status(400).send(error.details[0].message); } // 启动事务,所有后续操作全链路绑定同一session session.startTransaction(); const movie = await Movies.findById(req.body.movieID).session(session); const customer = await Customer.findById(req.body.customerID).session(session); if (!movie || !customer) { // 校验不通过时主动中止事务,释放会话 await session.abortTransaction(); return res.status(400).send("Please check the customer or Movie ID"); } movie.numberOfStock--; customer.numberOfRental++; // 保存操作显式绑定会话,纳入事务原子性管控范围 await movie.save({ session }); await customer.save({ session }); await session.commitTransaction(); } catch (e) { // 异常触发时中止事务,所有绑定该会话的操作将全部回滚 await session.abortTransaction(); console.log(e); res.status(500).send("Internal System error"); } finally { // 无论走哪个分支,确保会话正常释放 await session.endSession(); }
实践注意点
- 环境前置要求:事务功能需要MongoDB副本集架构支持,单机模式下的事务会隐式失效,这是很多开发初期容易踩的环境坑
- 绑定会话可以直接通过查询、保存方法传入
.session(session)的链式调用实现,相比手动拼接$session配置可读性更强 - 所有可能产生数据库写入的操作,必须在调用时显式绑定会话,否则会被排除在事务管控范围外
内容的提问来源于stack exchange,提问作者Uday Singh
相关产品推荐
相关产品推荐

