不该用{company_id:"hashed",create_time:1},因company_id区分度低致数据倾斜,且create_time在哈希后丧失范围查询能力,引发广播;应前置高频查询字段、后置高区分度哈希字段。

复合分片键该不该用 {company_id: "hashed", create_time: 1}?
不该直接用。这个组合在真实业务中大概率导致数据倾斜和广播查询——company_id 区分度低(比如 SaaS 多租户场景下大量 company_id 重复),哈希后仍无法打散;而 create_time 放在哈希之后,就失去范围扫描能力,所有 {$gt: ISODate("...")} 查询都会广播到全部分片。
真正有效的做法是把高频查询字段前置、高区分度字段哈希补足:例如 {region: 1, user_id: "hashed"},其中 region 支持按地域路由 + 范围扫描,user_id 哈希保证单 region 内写入均匀。
-
region必须是业务中真实存在且非单调递增的字段(不能是_id或自增时间戳) - 哈希字段必须是单值、非数组、非浮点(或确保 ≤2⁵³)
- 避免同时建
{"company_id": "hashed"}和{"company_id": 1}两个索引,mongos 可能误选后者触发全量广播
怎么用 analyzeShardKey 验证分片键是否合理?
别靠经验拍脑袋。MongoDB 7.0 的 analyzeShardKey 是基于真实采样查询做分布模拟的工具,它会告诉你三件事:chunk 是否不均、是否存在广播风险、预估 chunk 数是否超标。
执行命令示例:db.visitor.analyzeShardKey({create_time: 1, user_id: "hashed"}, {sampleSize: 100000})
- 重点关注返回中的
imbalancedChunks字段:若 >5%,说明即使加了哈希,数据仍倾斜 - 看
broadcastOperations:如果值高,说明你设计的前缀字段没覆盖常见查询条件(比如漏了create_time范围条件) - 检查
estimatedChunkCount:超过 1000 个 chunk 会影响迁移效率,需考虑是否前缀粒度太细
explain("executionStats") 在分片集群里怎么看才不被误导?
默认 explain 显示的是单个分片的统计,不是全集群耗时。真正关键的是 executionStages.shards 下每个分片的 executionTimeMillisEstimate 分布。
- 如果某一分片耗时明显高于其他(比如 1200ms vs 其余均 ≤200ms),说明该分片存在热点或局部索引缺失
- 看到
stage: "SHARD_MERGE"是正常现象,但若伴随大量docsExamined差异,说明部分分片做了无效扫描 - 务必加上
maxTimeMS测试,否则慢查询可能被 mongos 静默截断,掩盖真实瓶颈
为什么范围分片和哈希分片不能混用?
MongoDB 不支持对同一个集合同时启用范围和哈希分片策略。所谓“混合”,实际是指用复合分片键实现逻辑混合效果:前缀走范围、后缀走哈希。
错误理解会导致两个后果:
- 试图对
{create_time: "hashed", user_id: 1}这种倒置结构做范围查询,mongos 无法路由,只能广播 - 误以为加了哈希就能解决所有写入热点,却忽略了前缀字段本身是否单调(如用
_id做前缀,依然会热点) - 区域分片(Zone Sharding)才是物理层面的“混合”手段,但它依赖手动配置 zone 和 range 绑定,和复合分片键不是同一层机制
最常被忽略的一点:分片键一旦选定并分片,就不能修改。所有优化都必须在建集合前完成验证,上线后再调,代价远超预期。











