Mongoose使用withTransaction出现无限重试及事务不匹配报错问题
Mongoose withTransaction事务报错及无限重试问题排查
问题现象
使用Mongoose的withTransaction处理事务时出现两类报错,且触发无限重试(错误日志显示已重试1998次仍未停止),核心报错信息如下:
Mongodb Error withTransaction: Given transaction number 1 does not match any in-progress transactions. The active transaction number is -1
运行环境
- MongoDB Server 4.4.x
- mongoose ^5.10.0
withTransaction官方设计逻辑
Mongoose官方将withTransaction标注为事务最佳实践,该方法会自动处理以下逻辑:
- 创建事务
- 操作执行成功时提交事务
- 操作抛出异常时中止事务
- 出现临时事务错误时自动重试
问题代码
try { const session = await db.startSession(); await session.withTransaction(() => { console.log('transaction in progress') return new Promise((resolve, reject) => { Promise.all([ Model1().bulkWrite([...], { session }), Model2().bulkWrite([...], { session }) ]) .then(resp => { console.log('bulk write resolved successfully') resolve(resp) }) .catch(error => { console.log('transaction error', error) reject(error) }) }) }) } catch (error) { console.error('withTransaction: ', error) throw error } finally { session.endSession() }
报错详情
错误1:NoSuchTransaction
transaction in progress transaction error MongoError: Given transaction number 1998 does not match any in-progress transactions. The active transaction number is 1997 at MessageStream.messageHandler (/var/task/node_modules/mongodb/lib/cmap/connection.js:299:20) at MessageStream.emit (events.js:400:28) at processIncomingData (/var/task/node_modules/mongodb/lib/cmap/message_stream.js:144:12) at MessageStream._write (/var/task/node_modules/mongodb/lib/cmap/message_stream.js:42:5) at writeOrBuffer (internal/streams/writable.js:358:12) at MessageStream.Writable.write (internal/streams/writable.js:303:10) at TLSSocket.ondata (internal/streams/readable.js:726:22) at TLSSocket.emit (events.js:400:28) at addChunk (internal/streams/readable.js:290:12) at readableAddChunk (internal/streams/readable.js:265:9) at TLSSocket.Readable.push (internal/streams/readable.js:204:10) at TLSWrap.onStreamRead (internal/stream_base_commons.js:188:23) { operationTime: Timestamp { _bsontype: 'Timestamp', low_: 1, high_: 1634234124 }, ok: 0, code: 251, codeName: 'NoSuchTransaction', '$clusterTime': { clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 1, high_: 1634234124 }, signature: { hash: [Binary], keyId: [Long] } }, [Symbol(errorLabels)]: Set(1) { 'TransientTransactionError' } }
错误2:WriteConflict
transaction in progress transaction error MongoError: WriteConflict error: this operation conflicted with another operation. Please retry your operation or multi-document transaction. at MessageStream.messageHandler (/var/task/node_modules/mongodb/lib/cmap/connection.js:299:20) at MessageStream.emit (events.js:400:28) at processIncomingData (/var/task/node_modules/mongodb/lib/cmap/message_stream.js:144:12) at MessageStream._write (/var/task/node_modules/mongodb/lib/cmap/message_stream.js:42:5) at writeOrBuffer (internal/streams/writable.js:358:12) at MessageStream.Writable.write (internal/streams/writable.js:303:10) at TLSSocket.ondata (internal/streams/readable.js:726:22) at TLSSocket.emit (events.js:400:28) at addChunk (internal/streams/readable.js:290:12) at readableAddChunk (internal/streams/readable.js:265:9) at TLSSocket.Readable.push (internal/streams/readable.js:204:10) at TLSWrap.onStreamRead (internal/stream_base_commons.js:188:23) { operationTime: Timestamp { _bsontype: 'Timestamp', low_: 1, high_: 1634234124 }, ok: 0, code: 112, codeName: 'WriteConflict', '$clusterTime': { clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 1, high_: 1634234124 }, signature: { hash: [Binary], keyId: [Long] } }, [Symbol(errorLabels)]: Set(1) { 'TransientTransactionError' } }
根因分析与解决方案
核心错误点
bulkWrite是Mongoose Model的静态方法,不能在Model实例上调用。问题代码中Model1().bulkWrite()的写法是先创建了Model1的实例,再调用实例上不存在的bulkWrite方法,导致session没有正确绑定到操作上,操作未加入当前事务,触发事务编号不匹配的错误。
解决方案
- 修正
bulkWrite调用方式,直接调用Model的静态方法,确保session正确传递:
// 修正前(错误):Model1()返回实例,无静态bulkWrite方法 // Model1().bulkWrite([...], { session }) // 修正后(正确):直接调用Model的静态bulkWrite Model1.bulkWrite([...], { session })
- 限制事务重试次数,避免无限重试。
withTransaction默认对标记为TransientTransactionError的错误无重试上限,可通过配置maxRetryTimeMS参数限制最大重试时间:
await session.withTransaction(async () => { // 事务执行逻辑 }, { maxRetryTimeMS: 5000 // 最多重试5秒,可根据业务调整 })
- 排查并发操作冲突。
WriteConflict报错说明有其他并发事务同时修改同一批数据,可通过调整业务逻辑避免同时操作相同资源,或增加分布式锁控制操作时序减少冲突概率。 - 若业务需要在事务内创建并保存单条文档,使用实例的
save方法并传递session即可:
const newDoc = new Model1({ /* 文档字段 */ }); await newDoc.save({ session });
内容的提问来源于stack exchange,提问作者MTA
相关产品推荐
相关产品推荐

