分片事务失败主因是分片键缺失或错误、混入未分片集合、分片键值不稳定;须确保每条操作带完整分片键、所有集合已分片、分片键字段存在且类型匹配。

分片事务失败,八成是分片键没带全或选错了——不是事务本身有问题,而是路由根本走不通。
事务内所有操作必须命中同一分片
mongos 在解析事务时,会逐条检查每个 find、updateOne、insertOne 的查询条件是否能唯一确定目标分片。只要其中一条不带分片键,或分片键值为 null / 数组 / 类型不匹配,整个事务启动就直接报 IllegalOp。
- 确保事务里每条操作都显式带上完整分片键,比如分片键是
{ tenant_id: 1, order_id: 1 },就不能只写{ tenant_id: "abc" }; - 避免在事务中使用
$in、$gt等范围操作——哈希分片下它们仍可能跨分片,除非你确认所有值都落在同一分片(可通过sh.findShard("db.coll", { tenant_id: "abc", order_id: 123 })验证); - 如果业务允许,优先用哈希分片键(如
{ user_id: "hashed" }),$eq查询天然路由精准,但代价是无法做范围扫描。
别让未分片集合混进事务体
事务一旦触碰到任何未启用分片的集合(哪怕只是日志表、审计表、或 config/local 库里的集合),startTransaction 就立刻失败,错误信息是 TransactionNotSupported 或 IllegalOp,不是执行中途报错。
- 用
db.getSiblingDB("target_db").runCommand({ listCollections: 1 })检查每个集合返回结果里是否有shardKey字段; - 代码里别硬编码
db.collection("log"),改用database.getCollection("log")并提前断言coll.stats().sharded === true; - ORM 或 SDK 自动建集合时,务必关掉
autoCreate,所有集合必须由 DBA 预先执行sh.shardCollection。
分片键字段值必须稳定且可路由
分片键字段若在文档中缺失、为 null、或类型与索引定义不一致(比如索引建的是 String,但实际存了 Number),mongos 就无法定位分片,事务直接拒收。
- 插入前校验分片键字段是否存在且非空:用
validate: true启用集合级验证器,或在应用层拦截; - 避免用时间戳、自增 ID 这类单调值作分片键前缀——会导致新写入全部打到同一分片,形成热点;
- 复合分片键中,后缀字段(如
{ region: 1, seq: 1 })要保证seq在region内足够离散,否则块迁移跟不上写入节奏。
最易被忽略的是:事务里看似无关的辅助操作(比如更新一个统计计数器、写一条 trace 日志)往往悄悄引入未分片集合或缺失分片键的查询,而这类错误在测试环境因数据量小、分片少常被掩盖,上线后才集中爆发。











