PyMongo 4.0+ 且 MongoDB 4.0+ 副本集(或 4.2+ 分片集群)是使用原生事务的前提;必须显式指定 replicaSet、绑定 session、手动 abort 回滚,否则事务失败或阻塞。

PyMongo 4.0+ 才支持 MongoDB 原生事务,低于该版本调用 start_transaction() 会直接报错 —— 这是绝大多数人踩坑的第一步。
确认 PyMongo 和 MongoDB 版本是否满足事务前提
MongoDB 事务要求服务端为 4.0+(副本集)或 4.2+(分片集群),客户端 PyMongo 必须 ≥ 4.0。低于这些版本,start_transaction() 调用会抛出 NotImplementedError 或静默失败。
- 检查 PyMongo 版本:
pip show pymongo,确保输出类似Version: 4.7.2 - 检查 MongoDB 服务状态:
db.version()返回"4.0.28"或更高;且必须运行在副本集模式(单节点 standalone 不支持事务) - 连接时必须显式指定 replica set 名称:
client = MongoClient("mongodb://localhost:27017/", replicaSet="rs0"),否则start_transaction()会报InvalidOperation: Transaction numbers require a replica set
正确开启并提交一个跨集合事务
事务必须在 session 上启动,所有操作需绑定同一 session,并显式 commit 或 abort。不能混用不同 session,也不能在事务外调用 commit_transaction()。
- 必须用
with client.start_session() as session:获取 session,再调用session.start_transaction() - 每个操作都要传入
session=session参数,例如:coll1.insert_one({...}, session=session)、coll2.update_one({...}, {...}, session=session) - 事务内禁止使用
find()以外的读操作(如count_documents())—— 它们默认不参与快照读,可能读到未提交数据;如需一致性读,请用session.start_transaction(read_concern=ReadConcern("majority")) - commit 前若发生异常,务必调用
session.abort_transaction(),否则该 session 可能被服务器标记为“僵死”,后续操作会卡住
with client.start_session() as session:
try:
session.start_transaction()
db.orders.insert_one({"item": "apple", "qty": 5}, session=session)
db.inventory.update_one({"item": "apple"}, {"$inc": {"qty": -5}}, session=session)
session.commit_transaction()
except Exception:
session.abort_transaction()
raise
事务内写操作失败时的回滚行为
MongoDB 事务是原子性的:只要没 commit,所有写操作对其他 session 不可见;但事务内部的错误不会自动回滚已执行的操作 —— 你得自己处理中间状态或依赖 abort。
-
insert_one()失败(如唯一键冲突)会抛出DuplicateKeyError,但前面的update_one()已生效,必须靠abort_transaction()撤销全部变更 - 事务中调用
session.commit_transaction()失败(如网络中断、主节点切换),服务器可能已提交部分操作 —— 此时无法保证幂等,应用层需设计补偿逻辑或 idempotent key - 避免在事务里做耗时操作(如 HTTP 请求、文件 IO),超时(默认 60 秒)会导致自动 abort,且 session 不可重用
事务性能与连接池注意事项
事务会占用 session 和底层连接资源,高并发下容易成为瓶颈。PyMongo 默认连接池大小为 100,但每个活跃事务至少独占一个连接。
- 不要复用 session:每次事务必须新建 session,旧 session 在 commit/abort 后即失效
- 避免长事务:MongoDB 事务最大运行时间默认 60 秒(可通过
transactionLifetimeLimitSeconds修改),超时后整个 session 被丢弃 - 写密集场景慎用跨集合事务:它会锁住涉及文档的 _id 范围,可能阻塞其他写请求;单集合内多文档更新,优先考虑
bulk_write()+ordered=False - 监控
currentOp中"secs_running" > 30的事务,它们往往是慢查询或未关闭的 session
事务不是银弹。真正需要强一致性的场景才用它;多数增删改查走默认的单文档原子性就够了。最容易被忽略的是:副本集配置、session 绑定、以及 abort 的兜底逻辑 —— 少写一行 session.abort_transaction(),就可能让下游服务卡住几秒甚至更久。











