refinecollectionshardkey仅适用于零文档集合,要求新分片键为原键严格超集且需后续显式shardcollection;非空集合必须重建集合迁移。

refineCollectionShardKey 只对“零文档”集合有效
它根本不是用来“修改已存在分片键”的工具,而是专为那种刚执行过 sh.enableSharding("db.coll")、但还没插入任何数据的集合设计的。一旦你调用过 db.coll.insertOne({}),哪怕只插了一条,refineCollectionShardKey 就会立刻报错:cannot refine shard key on non-empty collection 或 CommandNotSupportedOnCluster。
常见误操作是:先启分片、建索引、试插一条测试数据确认连通性,再想用 refine 加字段——此时已彻底失效。生产环境几乎不可能满足“已启分片 + 零文档”这个窗口,别在已有业务数据的集合上浪费时间尝试。
新分片键必须是原键的严格超集
即使集合为空,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 就不是选项。真实可行方案只有一条:新建目标分片键集合 → 导出导入数据 → 手动补全索引 → 处理增量同步 → 切换流量。
关键细节常被跳过:
-
mongorestore --drop不会自动重建 TTL 索引、部分唯一索引、非分片键上的唯一约束,必须手动createIndex - 迁移窗口期的增量数据不会自动同步,得靠应用双写或解析 oplog 追平,MongoDB 本身不提供该能力
- 分片键字段值插入后不可修改(
$set也会报cannot modify shard key),所以新集合字段定义必须一次到位
最易被低估的复杂点是:一致性校验和回滚预案。没有原子切换,就没有真正平滑——所谓“平滑”,其实是把风险从单次操作分散到多个可控步骤里。











