refineCollectionShardKey仅适用于完全空的分片集合,要求新分片键为原键严格超集、存在对应索引、FCV≥4.4,且需后续显式shardCollection;有数据时必须重建集合。

refineCollectionShardKey 只适用于空分片集合
你不能用 refineCollectionShardKey 修改已有数据的分片键,哪怕只加一个字段。这个命令唯一合法的使用场景是:集合已执行过 sh.enableSharding("db.coll"),但**从未插入过任何文档**。一旦 db.coll.insertOne({}) 成功执行,该集合就“冻结”了分片键,refineCollectionShardKey 就会直接报错 CommandNotSupportedOnCluster 或 cannot refine shard key on non-empty collection。
新键必须是原键的严格超集
即使集合为空,refineCollectionShardKey 也强制要求新键必须包含原键全部字段,且顺序、类型、方向(1/-1)完全一致,只能在末尾追加字段。比如原键是 {"tenantId": 1},你只能改成 {"tenantId": 1, "userId": 1} 或 {"tenantId": 1, "createdAt": -1};不能改成 {"userId": 1},也不能把 tenantId 改成 -1,更不能加 "hashed" —— 哈希分片键不支持复合 refine。
- 操作前必须存在支撑索引:例如新键为
{"tenantId": 1, "userId": 1},就得先建db.coll.createIndex({"tenantId": 1, "userId": 1}) - FCV 必须 ≥ 4.4:
db.adminCommand({getFeatureCompatibilityVersion: 1})确认返回值 - 权限需含
refineCollectionShardKey动作,clusterManager角色可满足
refine 后仍需显式调用 shardCollection
refineCollectionShardKey 只更新元数据,不触发实际分片。它不会创建 chunk,也不会路由写入。你必须紧接着执行 sh.shardCollection("db.coll", {"tenantId": 1, "userId": 1}),否则集合仍是“已启用分片但未真正分片”的状态,所有写入都会打到主分片(primary shard)上,失去水平扩展意义。
这个两步操作不可逆:一旦 sh.shardCollection 执行成功,键就彻底锁定,后续再无任何官方手段修改。
真正有数据时,别碰 refine,老实用重建
生产环境几乎不可能满足“已启分片但零文档”这个条件。只要集合里有一条记录,refineCollectionShardKey 就是死路。此时唯一路径是新建集合、迁移数据、切换应用流量。容易被忽略的关键点是:
- 旧集合的 TTL 索引、部分唯一索引、非分片键上的唯一约束,
mongorestore --drop不会自动重建,必须手动补全 - 迁移窗口期的增量数据必须靠应用双写或 oplog 解析追平,MongoDB 不提供自动同步机制
- 分片键字段本身不可更新 —— 即使是
$set也会报cannot modify shard key,所以旧数据若键值不满足新策略(如含数组),导入阶段就会失败
refine 的设计初衷不是“救火”,而是防止刚启分片就选错键的极窄窗口补救。线上有数据?直接规划重建方案,别在 refine 上浪费时间。











