$shardeddatadistribution 不会显示“强制走某 shard”的效果,因为它仅基于 config.shards 和 config.chunks 元数据做静态统计,不触发真实查询、不感知 hint() 或 readpreference,也不影响 mongos 的路由决策。

为什么 $shardedDataDistribution 不会显示“强制走某 Shard”的效果
$shardedDataDistribution 是 MongoDB 6.0+ 引入的聚合阶段,用于估算分片键值在各 Shard 上的分布比例,但它本身不参与查询路由,也不影响执行计划。它只是读取 config.shards 和 config.chunks 的元数据做静态统计,不会触发真实查询、不感知 hint()、不响应 readPreference 或 shardFilter。所以你无论怎么“强制”,这个阶段输出的分布比例都不会变——它压根没去查数据。
真正能“强制走特定 Shard”的只有两种合法方式
MongoDB 分片集群中,路由层(mongos)决定查询发往哪个 Shard,唯一可控入口是:分片键值 + 查询条件 + 路由策略。所谓“强制”,本质是让 mongos 精确匹配到单个 Chunk,绕过广播或范围扫描。
- ✅ 精确匹配分片键全字段:比如分片键是
{region: 1, userId: 1},必须同时指定{region: "us-east", userId: 12345},才能命中唯一 Shard;只查{region: "us-east"}仍可能跨多个 Shard - ✅ 使用
hint("_id_")+_id精确值(仅当_id是分片键或含在分片键中):否则hint在分片环境下被忽略,mongos 直接报错"hint not supported for sharded collections" - ❌
readPreference: {mode: "primary", tags: [{shard: "shard01"}]}:tags 只影响副本集内节点选择,不影响 mongos 到哪个 Shard 发请求 - ❌ 在聚合管道里加
$shardedDataDistribution后再$match:它不改路由,只是对统计结果过滤,实际查询早已按原始条件分发完毕
如何验证查询是否真的只去了一个 Shard
别信 explain("executionStats") 里的 nReturned 或 totalDocsExamined,它们是合并后的汇总值。要看真实分发路径,必须抓 mongos 日志或用 db.currentOp({secs_running: {$gt: 0}}) 观察 active 操作的 shard 字段:
db.runCommand({
aggregate: "orders",
pipeline: [
{ $match: { region: "us-west", userId: 98765 } },
{ $shardedDataDistribution: {} }
],
cursor: {}
})
然后立刻在 mongos 日志中搜 "shard: \"shard02\"" —— 如果只出现一次且无其他 shard 名,说明成功单 Shard 执行;如果看到 shard01、shard02 多次交替出现,说明条件没锁死分片键,仍在广播。
监控分布时容易误读 $shardedDataDistribution 的三个点
这个阶段返回的是 chunk 数量占比和文档数估算值,但不是实时采样,也不反映写入倾斜:
- 它依赖
config.chunks中的min/max范围和documentCount字段,而后者由 balancer 定期更新(默认每 60 秒),新写入的数据还没被 chunk 统计捕获时,数值就滞后 - 如果用了哈希分片键,
$shardedDataDistribution显示各 Shard 文档数接近,但实际userId: Hash(123)和userId: Hash(456)可能落在同一 chunk,导致单 Shard 短时 CPU 飙高——分布均匀 ≠ 负载均衡 - 它不区分主从:即使你把读流量切到某个 Shard 的 secondary,
$shardedDataDistribution输出也不会体现该 Shard 的读压力,它只看 chunk 元数据
真要定位热点 Shard,得结合 sh.status() 查 chunk 迁移记录、db.collection.stats().sharded 看每个 Shard 的 count、以及 mongostat --host <mongos></mongos> 的 netIn/netOut 按 Shard 拆分看流量。











