mongodb事务必须使用副本集+显式session+同库集合,单节点不支持;mongoid需通过driver session操作collection,模型方法如user.create不参与事务。

必须用副本集 + 显式 session + 同库集合,否则事务根本不会生效。
确认 MongoDB 部署满足事务前提
单节点 mongod 实例不支持事务,哪怕版本是 6.0 也会报错 Transaction numbers are only allowed on a replica set member or mongos。本地开发务必启动副本集:
- 命令行启动:
mongod --replSet rs0 --port 27017,然后连上执行rs.initiate() - Docker 启动需加
--replSet rs0参数,并在容器内调用rs.initiate() - 检查状态:运行
rs.status(),看到members列表且至少一个成员状态为PRIMARY - 云服务(如 Atlas)默认启用副本集,但要确认连接字符串中包含
replicaSet=参数
在 Mongoid 中正确开启和使用事务会话
Mongoid 本身不封装事务逻辑,必须通过底层 Ruby driver 的 session 对象操作。所有 CRUD 必须走该 session 绑定的 collection 实例,否则写入直接脱离事务上下文。
- 不能写:
User.create(...)或db.collection("users").insert_one(...)—— 这些不经过 session - 必须写:
session.client.db("myapp").collection("users").insert_one(...) - 推荐封装方式:在 Rails console 或 service 类中先获取 session:
session = Mongoid.default_client.start_session - 事务块内必须显式调用
session.commit_transaction或session.abort_transaction;仅endSession不会提交
示例(Rails console):
session = Mongoid.default_client.start_session
begin
session.start_transaction
users = session.client.db("myapp").collection("users")
orders = session.client.db("myapp").collection("orders")
users.insert_one({ name: "Alice" })
orders.insert_one({ user_id: BSON::ObjectId.new, status: "created" })
session.commit_transaction
rescue => e
session.abort_transaction
raise e
ensure
session.end_session
end
避免常见事务失败场景
事务 commit 失败往往不报明显错误,而是静默回滚或抛出模糊异常,根源常在约束或操作类型上:
- 跨数据库操作:
db1.users和db2.orders无法共处一事务 → 全部集合必须在同一个数据库下(如统一用myapp) - 用了禁用命令:事务中调用
create_index、distinct、$lookup(无 pipeline 时)、mapReduce会直接失败 - 集合类型不兼容:capped collection、
system.*集合、collation 非simple且未设readConcern: "snapshot"的集合均不支持 - 超时:默认 60 秒,长事务需在
start_transaction时传maxTimeMS参数
事务与 Mongoid 模型方法不自动兼容
User.create、Order.update 等 Mongoid 实例方法默认不接受 session 参数,也不能直接参与事务。你不能写 User.create(..., session: session) —— 这个参数被忽略。
- 若坚持用模型层,需手动将数据转为 Hash,再用 session.collection 插入:
session.client.db("myapp").collection("users").insert_one(user.as_document) - 验证逻辑(如
validates :email, uniqueness: true)在事务中依然有效,但唯一性检查依赖数据库索引,需确保索引已建且覆盖事务查询路径 - 嵌入式文档(
embeds_one/embeds_many)的操作天然在父文档内,不涉及多文档事务,无需 session
真正需要事务的场景,比如「扣库存 + 创建订单」,建议绕过 Mongoid 模型方法,直操作 collection,控制粒度更明确,也更容易调试失败点。











