事务启动直接失败报IllegalOp,大概率因操作集合未启用分片:未分片集合、系统库或分片键缺失/不匹配均导致解析阶段即拒绝,须通过sh.status()、coll.stats()及sh.findShard()实锤验证。

事务启动直接失败,报 IllegalOp 或 TransactionNotSupported,大概率不是代码写错了,而是事务里操作的集合压根没在分片集群中启用分片 —— MongoDB 会当场拒绝,不给任何机会。
检查目标集合是否真已分片
事务对未分片集合的写入是硬性禁止的,哪怕只有一条 insertOne 落在非分片库或未分片集合上,整个事务解析阶段就会中断。别信“它以前能跑”,得实锤确认:
- 连接
mongos执行sh.status(),看目标集合是否出现在shards列表下;没出现就说明没分片 - 用
db.getSiblingDB("mydb").getCollection("mycoll").stats()查返回结果中是否有"sharded": true字段;没有就是未分片 - 若用的是 Atlas 或云托管服务,进控制台查该集合是否标记为 “Sharded”;有些平台 UI 上显示“已启用分片”,但实际漏执行了
sh.shardCollection
避免事务混入 config / local / admin 库操作
这些系统库天然不支持分片,也不允许事务写入。常见误操作包括:
- 在事务回调里调用
db.adminCommand({setParameter: 1, ...})—— 这不是数据操作,是管理命令,MongoDB 直接拒掉 - 日志写入
db.getSiblingDB("audit").logs.insertOne(...),但audit库根本没执行过sh.enableSharding("audit") - ORM 或 SDK 自动创建临时集合(如 Hibernate 的
_schema表),而这些集合默认不会被自动分片
解决办法:事务体里所有 database.getCollection("xxx") 调用前,加断言 coll.stats().sharded === true;硬编码库名的逻辑一律改用运行时注入,并由部署流程确保预分片。
分片键缺失或不匹配导致路由失败
即使集合已分片,事务内查询若没带全分片键,mongos 就无法确定目标分片,会拒绝执行事务 —— 不是报错慢,是 startTransaction 阶段就卡住或返回 IllegalOp:
- 复合分片键
{ tenant_id: 1, order_id: 1 },但事务里写的是coll.updateMany({ tenant_id: "abc" }, ...),缺order_id,路由失败 - 哈希分片键
{ _id: "hashed" },但用了$in或$gt查询,可能跨分片,MongoDB 不允许 - 分片键字段值为
null或数组,文档会被散列到不同分片,事务必然崩
验证方式:用 sh.findShard("db.coll", { tenant_id: "abc", order_id: 123 }) 看是否稳定返回同一分片;所有事务内读写操作的 filter 必须包含完整、非空、类型一致的分片键。
客户端连接和会话配置必须匹配分片拓扑
事务不是光靠代码写对就行,底层连接必须走 mongos,且会话生命周期要兜住整个事务:
- 连接字符串不能是单节点
mongodb://localhost:27017,必须是mongodb://mongos1:27017,mongos2:27017/?directConnection=false - Java/Python 客户端创建
ClientSession后,所有collection.insertOne()等操作必须显式传入该 session 实例;复用全局 collection 对象却没传 session,等于没开事务 - Spring Data MongoDB 若用
@Transactional,需确认是否启用了MongoTransactionManager,且底层MongoTemplate是基于mongos的连接
最隐蔽的坑是:开发环境连副本集(支持事务),生产连分片集群(也支持),但连接串写错了,或者 CI/CD 没替换配置 —— 看似一样的代码,在两边行为完全不同。


















