refineCollectionShardKey仅适用于零文档集合,要求新键为原键严格超集且必须显式调用shardCollection才生效;有数据时唯一可行方案是重建集合并迁移数据。

refineCollectionShardKey 只对零文档集合有效
它根本不是用来“修改已有分片键”的工具,而是专为刚执行过 sh.enableSharding("db.coll")、但一条数据都还没插入的集合设计的。一旦你调用过 db.coll.insertOne({}),哪怕只插了一条测试数据,refineCollectionShardKey 就会立刻报错:cannot refine shard key on non-empty collection 或 CommandNotSupportedOnCluster。
常见误操作是:先启分片、建索引、试插一条确认连通性,再想加字段——此时已彻底失效。生产环境几乎不可能满足“已启分片 + 零文档”这个窗口,别在已有业务数据的集合上浪费时间尝试。
新分片键必须是原键的严格超集
即使集合为空,refineCollectionShardKey 也强制要求新键包含原键所有字段,且顺序、类型、方向(1 或 -1)完全一致,只能在末尾追加字段。
- 原键是
{"tenantId": 1}→ 合法新键:{"tenantId": 1, "userId": 1}或{"tenantId": 1, "createdAt": -1} - 非法操作包括:
{"userId": 1}(删了原字段)、{"tenantId": -1}(改了方向)、{"tenantId": "hashed"}(哈希不支持复合 refine)
错误现象通常是:new shard key must be a strict superset of the existing shard key。注意:这里“严格超集”指结构层面,不是值域层面。
refine 后必须显式调用 shardCollection 才真正分片
refineCollectionShardKey 只更新配置元数据,不创建 chunk,也不路由写入。它做完后,集合仍处于“已启用分片但未真正分片”的状态,所有写入都会打到主分片(primary shard)上,完全没用。
你必须紧接着执行:
sh.shardCollection("db.coll", {"tenantId": 1, "userId": 1})
否则等于白干。而且这一步执行后,分片键就彻底锁定——MongoDB 不提供任何后续修改手段。容易被忽略的是:这个两步操作不可逆,且 shardCollection 会触发初始 chunk 拆分和均衡,需避开业务高峰期。
有数据时唯一路径是重建集合
只要集合里存在任意一条文档,refineCollectionShardKey 就不是选项。真实可行方案只有一条:新建目标分片键集合 → 导出导入数据 → 手动补全索引 → 处理增量同步 → 切换流量。
容易被忽略的关键点是:
- 旧集合的 TTL 索引、部分唯一索引、非分片键上的唯一约束,
mongorestore --drop不会自动重建,必须手动补全 - 迁移窗口期的增量数据必须靠应用双写或 oplog 解析追平,MongoDB 不提供自动同步机制
真正上线前,务必验证新键是否覆盖高频查询的前缀字段——否则查询仍会广播到所有分片,性能问题照旧。











