refineCollectionShardKey仅支持在现有分片键末尾追加字段形成前缀扩展,不可删改原字段、变更顺序或修改类型;要求新增字段全量存在且非空,不迁移数据,仅更新元数据和路由逻辑。

refineCollectionShardKey 不是用来“更换”分片键的,它只允许你在**现有分片键基础上追加后缀字段**,形成更精细的复合分片键。不能删掉原字段,也不能改顺序,更不能把 "hashed" 改成 1 或反之。
refineCollectionShardKey 能做什么、不能做什么
这个命令本质是“扩展”而非“重写”分片键。比如你原来用 { "userId": 1 } 分片,现在想加入租户隔离,可以 refine 成 { "userId": 1, "tenantId": 1 };但不能改成 { "tenantId": 1 },也不能把 userId 换成哈希。
- 必须保证新键是旧键的**前缀扩展**(即旧键字段全保留,且顺序不变,只在末尾加字段)
- 新增字段必须已存在于所有文档中,且不能为
null(否则 refine 失败) - 不触发数据迁移 —— 它只更新元数据和索引结构,原有 chunk 分布不变
- 执行后,新插入/更新的文档会按新键路由,但老 chunk 仍按旧键逻辑归属,均衡器后续会逐步拆分
实际执行时要检查的几件事
直接运行前,先确认三件事,否则命令会静默失败或报错:
- 集合必须已分片 ——
sh.status()能看到该集合出现在 sharded collections 列表里 - 目标新增字段必须有**支持的索引**:例如你要加
"tenantId",就得先确保存在索引{ "userId": 1, "tenantId": 1 }(或至少{ "tenantId": 1 },但推荐复合) - 集群必须运行 MongoDB ≥ 5.0,且
featureCompatibilityVersion已设为"5.0"或更高(db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })查)
怎么调用 refineCollectionShardKey
它是个 adminCommand,不是 shell 方法,得用 db.adminCommand() 调用:
db.adminCommand({
refineCollectionShardKey: "mydb.users",
key: { "userId": 1, "tenantId": 1 }
})
注意:key 字段必须包含**全部原有分片键字段 + 新增字段**,顺序不能错。如果原来就是 { "userId": "hashed" },那 refine 后只能是 { "userId": "hashed", "tenantId": 1 } —— 哈希字段只能放在最前面,后面接范围字段。
- 执行成功返回
{ ok: 1 },但不会告诉你索引是否自动创建;建议手动db.users.getIndexes()确认新复合索引是否存在 - 如果提示
"Refining shard key requires the collection to be sharded",说明还没分片,先跑sh.shardCollection("mydb.users", { "userId": 1 }) - 如果报
"cannot refine shard key: field xxx is missing from some documents",说明有文档缺失新增字段,得先补全
refine 后查询和写入行为变化
refine 完成后,路由逻辑立刻生效,但数据物理分布不会重洗 —— 这点和 reshardCollection 有本质区别:
- 带完整新键的查询(如
{ userId: 123, tenantId: "t-a" })能精准路由到单个 shard - 只带旧键的查询(如
{ userId: 123 })仍会广播到多个 shard,因为 chunk 边界还是按旧键划分的 - 写入新文档时,如果缺失
tenantId字段,会直接报错"Document does not contain shard key" - 真正实现均匀分布,得等均衡器自动拆分老 chunk —— 可以手动触发
sh.splitAt("mydb.users", { userId: 123, tenantId: "t-a" })加速
最容易被忽略的是:refine 不解决数据倾斜,它只是为后续更细粒度拆分铺路;如果你的原始 userId 本身分布极不均,加 tenantId 也救不了 —— 那时候该考虑 reshardCollection 换键了。


















