片键设计缺陷是分片集群性能问题的根源,需满足高基数、写入分散、查询覆盖三硬指标,否则调优无效。

小粒度片键直接导致Jumbo Chunk和迁移卡死
片键区分度太低(比如只用 status 字段,只有 "active"/"inactive" 两种值),会让所有匹配同一值的文档全塞进同一个 chunk。这个 chunk 永远无法按片键分裂——因为分裂点必须落在两个不同片键值之间,而它内部全是同一个值。结果就是生成 Jumbo Chunk(远超 64MB),moveChunk 失败率飙升,balancer 反复重试却无法推进,日志里堆满 cannot move chunk: { code: 207 }。
频繁迁移不是均衡,是路由表雪崩前兆
当片键本身粒度细(如毫秒级时间戳、递增 ID)+ chunkSize 又被人为调小(比如设成 16MB),就会触发“写入→满额→分裂→迁移→再满额→再分裂”的恶性循环。关键问题不在迁移本身,而在每次迁移都强制更新 config server 的 chunks 集合,并广播给所有 mongos 节点刷新路由缓存。实测中,单次 moveChunk 可能引发 3–5 秒的路由表抖动,期间跨分片查询会降级为 scatter-gather,延迟毛刺明显。
- mongos 路由缓存失效不是原子操作,旧缓存残留会导致部分请求发错 shard
- config server 的
chunks集合写入压力剧增,容易成为集群瓶颈 - balancer 每 10 秒轮询一次(6.0.3+ 改为按数据量差判断,但小片键仍会放大偏差)
哈希分片也不能救烂片键
有人试图用哈希分片掩盖片键设计缺陷,但这是错觉。哈希只解决分布均匀性,不解决查询模式适配性。如果业务查询总带 { created_at: { $gt: ... } } 这种范围条件,而片键是哈希过的 _id,那每次查询都得打到所有 shard 上——这就是分散-聚集(scatter-gather)。此时 chunk 再小、迁移再勤,对查询性能毫无帮助,反而因更多 chunk 导致更多的网络跳转和聚合开销。
- 哈希分片下,
min/max范围失去语义,所有范围查询退化为全分片扫描 - 监控时发现
sh.status().shards显示 chunk 数均衡,但db.collection.stats()显示各 shard 磁盘使用率差 3 倍以上,说明数据量根本没均衡 - 6.0.3+ 版本 balancer 已改用数据量差(>384MB)触发迁移,但小粒度片键造成的写入倾斜会让这个阈值形同虚设
真正该检查的三个地方
别急着调 chunkSize 或关 balancer。先确认片键是否满足三个硬指标:
- 高基数:字段取值数 ≥ 预期 shard 数 × 100(例如 8 个 shard,至少 800 个不同值)
- 写入分散:新文档的片键值不能集中在某个窄区间(避免时间戳、自增 ID 单点写入)
- 查询覆盖:80% 以上高频查询都含该字段的等值条件(否则无法路由到单个 shard)
这三个点里任意一个不满足,后续所有 chunk 调优都是在给漏水的桶加水。尤其注意:sh.splitAt() 和 sh.moveChunk() 手动干预只能缓解症状,无法根治片键缺陷本身。











