事务提交后文档没更新但 commitTransaction() 无报错,通常因操作未传入 session 选项导致脱离事务上下文;必须确保每个 CRUD 操作(如 updateOne、create)显式携带 { session },且所有 await 异步调用不被 .then() 链式调用破坏执行流。

事务提交后文档没更新,但 commitTransaction() 没报错
这通常不是事务失败,而是你没在事务会话里执行操作。Mongoose 的事务必须显式传入 session 选项,否则所有操作都走默认连接,跟事务无关。
常见错误写法:
const session = await mongoose.startSession();
session.startTransaction();
await User.updateOne({ _id: userId }, { $inc: { balance: -100 } }); // ❌ 没传 session,操作不在事务内
await Order.create({ userId, amount: 100 }); // ❌ 同样没 session
await session.commitTransaction(); // ✅ 成功,但两个操作根本没进事务
正确做法是每个模型操作都加 { session }:
User.updateOne(filter, update, { session })Order.create(doc, { session })-
await session.commitTransaction()前,所有操作必须带session
事务回滚了但日志显示 commitTransaction() 成功
这不是 Mongoose 的 bug,而是 MongoDB 的行为:如果事务中某个操作抛出可捕获错误(比如 ValidationError),而你没 throw 或 await 它,commitTransaction() 仍会执行——但它实际提交的是空事务(什么都没改)。
典型陷阱:
try {
await User.findById(userId).then(u => u.balance -= 100); // ❌ 链式调用没 await,错误被吞
await Order.create({ userId }, { session });
await session.commitTransaction();
} catch (err) {
await session.abortTransaction();
}
务必确保每个异步操作都被 await,且错误能冒泡到外层 catch:
- 避免
.then()链式调用,统一用await - 不要在事务块内用
try/catch局部捕获,除非你明确要处理并继续 - 所有
await操作必须在session上下文中
事务里用了 save() 却发现字段没校验或默认值没生效
save() 在事务中行为和普通调用一致,但容易忽略两点:一是 schema 的 strict 设置,二是 pre('save') 中间件是否依赖外部状态(比如当前时间、缓存数据)。
例如:
userSchema.pre('save', function() {
this.updatedAt = Date.now(); // ✅ 安全,纯同步
});
userSchema.pre('save', async function() {
this.token = await generateToken(this._id); // ❌ 危险!事务中不能依赖外部异步服务
});
事务要求所有操作可重试、无副作用,所以:
- 避免在
pre('save')里调用数据库查询、HTTP 请求、文件读写 - 确保
default字段值是同步可计算的(如Date.now、uuidv4()) - 若必须异步生成值,提前算好再传入构造函数,不要塞进中间件
副本集没配好,事务直接报 Transaction numbers 错误
MongoDB 事务强制要求副本集(哪怕单节点也要初始化为副本集),否则 startSession() 可能成功,但 commitTransaction() 会失败并抛出类似 Transaction numbers are not supported in standalone mode 的错误。
验证方式很简单:
mongo --eval "rs.status()" # 如果报错 "not running with --replSet",说明没启用副本集
本地开发快速启用(注意端口别冲突):
mongod --replSet rs0 --port 27017 --dbpath /data/db
# 连接后执行:
rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'localhost:27017' }] })
线上环境必须确认 MongoDB 实例以 --replSet 启动,且 Mongoose 连接字符串末尾加上 ?replicaSet=rs0。
事务最易被忽略的点是:它不解决业务逻辑错误,只保证原子性。比如扣款成功但发短信失败,事务不会回滚短信步骤——那得靠补偿机制或消息队列。别指望一个 session 包治百病。










