MongoDB 4.2分布式事务基于HLC与两阶段提交,需满足4个硬前提:服务端≥4.2且版本一致、驱动≥4.2、无仲裁器分片、writeConcernMajorityJournalDefault=true;必须用readConcern="snapshot",事务超时默认60秒,单事务≤16MB,Spring Boot需显式配置MongoTransactionManager及多mongos URI。

MongoDB 4.2 的分布式事务不是靠中心协调器“统一下令”,而是由 mongos 协同各 shard 基于混合逻辑时钟(HLC)和两阶段提交变体自主协作完成。它能跨分片、跨库、跨集合保证 ACID,但前提是配置和调用方式必须严格匹配约束条件。
事务启动前必须确认的 4 个硬性前提
缺一不可,否则事务直接失败或静默降级为非事务操作:
-
mongod版本 ≥ 4.2,且所有分片副本集成员、config server、mongos实例版本一致(混用 4.0/4.2 驱动或服务端会报Transaction numbers do not match或NotMasterNoSlaveOk) - 客户端驱动必须为 MongoDB 4.2+(Java Driver 4.0+、Node.js Driver 3.6+),旧驱动在多
mongos环境下可能返回InvalidOptions: cannot specify writeConcern in transaction - 所有参与分片不能含仲裁器(
arbiterOnly: true),否则跨分片写入会触发Cannot run transaction with arbiter-only shard - 所有分片的
writeConcernMajorityJournalDefault必须为true(即 journal 已启用),否则提交阶段抛出WriteConcernFailed: majority write concern is not satisfied
session.startTransaction() 的关键参数组合与陷阱
参数不是可选装饰,而是直接影响快照一致性与提交行为:
-
readConcern: { level: "snapshot" }是跨分片事务的强制要求;用"majority"或"local"会导致读取结果在不同分片间不一致,且事务提交后外部读可能看到部分写入(“分片可见性撕裂”) -
writeConcern: { w: "majority" }可显式指定,但最终提交操作总会追加{ w: "majority", j: true },所以不必重复设j: true,否则驱动可能报错 - 不要在事务内使用
explain()、killCursors或对system.*、admin、config库执行任何读写——这些操作在事务中直接被拒绝,错误信息为CommandNotFound: no such cmd: explain或Unauthorized: not authorized on config to execute command
事务超时与 oplog 溢出的实际应对方式
MongoDB 不允许“无限等待”,两个硬限制常被忽略导致静默回滚:
- 默认事务生命周期上限是
transactionLifetimeLimitSeconds = 60(秒),这个值需在所有分片副本集成员的配置中统一修改:setParameter: { transactionLifetimeLimitSeconds: 120 },仅改mongos或 config server 无效 - 单事务内所有修改文档总大小 ≤ 16MB,超过则在
commitTransaction时失败,报错TransactionTooLarge;若涉及大文档批量更新,必须拆分为多个事务或改用非事务批量写入 + 应用层补偿 - oplog 不再单条记录整个事务,而是按操作拆成多条,每条带
prepare: true/false和prevOpTime字段;这意味着 oplog 分析工具若未适配 4.2+ 格式,会丢失事务上下文关联
Spring Boot 中开启事务的最小可行配置
很多项目卡在 MongoTransactionManager 不生效,本质是自动配置未穿透到分片集群:
- 必须显式声明
@Bean的MongoTransactionManager,且传入的是SimpleMongoClientDatabaseFactory(而非过时的MongoDbFactory),否则事务上下文无法绑定到mongos连接 -
application.yml中不能只写spring.data.mongodb.uri,必须确保 URI 包含全部mongos地址(如mongodb://mongos1:27017,mongos2:27017),否则事务路由会固定打到单个mongos,失去负载与容错能力 - 事务方法必须用
@Transactional注解且所在类由 Spring 容器管理;若在@Service内部直接 new 出 DAO 实例调用,事务完全不触发——这是最常被日志误导的“事务没生效”原因
真正难的不是写 session.startTransaction(),而是让每个分片都认可同一个快照时间点、让 prepare 阶段所有 shard 同时响应、并在网络抖动时坚持等齐 commit 回执。这些细节藏在 config.transaction_coordinators 表和各 shard 的 transaction_coordinator.cpp 里,应用层看不见,但一旦配置越界,它就只给你一个模糊的 UnknownTransactionCommitResult。


















