MongoDB 4.2.6批量插入事务失败求助(ReactiveMongo驱动)
结合你的环境(MongoDB 4.2.6 + Play 2.7.x + ReactiveMongo 0.18.4-play27)和错误信息(错误码251,NoSuchTransaction),这个问题的核心原因是ReactiveMongo 0.18.x版本在事务中执行批量插入(insertMany)时,默认的多批次(multi-bulk)操作没有正确将事务会话上下文传递到所有批次请求。
当你执行insert(false).many(...)时,ReactiveMongo会根据文档数量自动拆分多个批量请求,而旧版本的驱动没有确保每个批量请求都绑定同一个事务会话,导致后续批次请求找不到已开启的事务,从而抛出"Given transaction number 1 does not match any in-progress transactions"的错误。而单条插入(insertOne)只发送一个请求,事务上下文能正确传递,所以可以正常执行。
另外,你的代码里还有一个小问题:在writeResultFuture.map中调用dbWithTnx.commitTransaction().flatMap{ _ => sessionDb.endSession() }会导致Future嵌套,无法正确等待事务提交和会话关闭完成,这可能会引发其他潜在问题。
解决办法
1. 强制单批次插入(快速修复)
禁用ReactiveMongo的自动多批次拆分,将所有文档放在一个批量请求中发送,确保事务上下文能正确传递。你可以通过设置maxDocsPerBulk为文档总数,确保所有文档在同一批次发送:
修正后的业务代码示例:
reactiveMongoApi.database.flatMap { db: DefaultDB => db.startSession().flatMap { case Some(sessionDb) => sessionDb.startTransaction(Option(WriteConcern.Default)) match { case None => Future.successful(println("Database replica is not initiated")) case Some(dbWithTnx: DefaultDB) => val collFoo = dbWithTnx.collection[BSONCollection]("foo") val docs = Seq(BSONDocument("name" -> "Jack"), BSONDocument("name" -> "Ma")) // 强制单批次插入,指定批量大小为文档总数 val writeResultFuture = collFoo.insert(false) .many(docs, maxDocsPerBulk = docs.size) // 用flatMap代替map,确保等待commit和endSession完成 writeResultFuture.flatMap { wr => println(wr) dbWithTnx.commitTransaction().flatMap { _ => sessionDb.endSession() } } } case None => Future.successful(println("None")) } }
2. 升级ReactiveMongo版本(彻底解决)
ReactiveMongo在后续版本(比如0.19.x及以上)中修复了事务中批量操作的上下文传递问题。你可以升级build.sbt中的依赖到最新兼容Play 2.7的版本:
libraryDependencies ++= Seq( "org.reactivemongo" %% "play2-reactivemongo" % "0.19.7-play27" )
注意:升级前请确认版本兼容性,避免API变动带来的适配问题。
3. 修正Future处理逻辑
确保所有异步操作(事务提交、会话关闭)都被正确等待,避免Future嵌套。使用flatMap代替map来处理后续的异步操作,保证整个流程的异步连贯性。
额外验证点
- 确认你的MongoDB集群是副本集(事务仅支持副本集,不支持单节点),从错误信息中的
TransientTransactionError来看集群状态正常,但可以再检查副本集运行状态。 - 事务中建议使用
WriteConcern.Majority来确保数据一致性,你可以在启动事务时指定该参数。
内容的提问来源于stack exchange,提问作者John

