{company_id: "hashed", create_time: 1}是危险组合,因company_id基数低导致哈希无法有效打散数据,引发倾斜;create_time置于哈希字段后丧失范围查询能力,使时间条件查询广播至全部分片,性能断崖下跌。

为什么{company_id: "hashed", create_time: 1}是危险组合
这个复合分片键在 SaaS 类多租户场景下极易引发数据倾斜和广播查询——company_id 基数低(大量租户共用同一 ID),哈希后仍无法打散;而 create_time 放在哈希字段之后,就彻底失去范围扫描能力。所有形如 {$gt: ISODate("2025-01-01")} 的查询都会广播到全部分片,性能断崖式下跌。
常见错误现象包括:
- mongos 日志中频繁出现
broadcast字样 - explain("executionStats") 显示
executionStages.shards下多个分片的docsExamined差异极大 - chunk 迁移频繁,
sh.status()中看到大量未均衡 chunk
怎么用 analyzeShardKey 验证分片键合理性
别靠经验猜。MongoDB 7.0 的 analyzeShardKey 是基于真实采样数据模拟分布的工具,必须跑起来看结果。
执行示例:
db.orders.analyzeShardKey({region: 1, user_id: "hashed"}, {sampleSize: 100000})
重点关注返回中的三个字段:
-
imbalancedChunks> 5%:说明即使加了哈希,单 region 内仍存在写入热点 -
broadcastOperations值高:前缀字段(如region)没覆盖你的真实查询条件(比如漏了status: "paid") -
estimatedChunkCount超过 1000:前缀粒度太细,后续 chunk 拆分/迁移开销会显著上升
复合分片键字段顺序与类型限制
顺序不是语法问题,而是路由逻辑问题:前缀字段决定 mongos 能否精准路由,后缀字段只负责打散。
必须满足:
- 前缀字段要是业务中真实存在、非单调递增的值,比如
region、category、tenant_type,不能是_id或自增时间戳 - 哈希字段必须是单值、非数组、非浮点;若为数字,确保 ≤ 2⁵³(避免哈希失真)
- 避免对同一字段建两个索引:
{"company_id": "hashed"}和{"company_id": 1}共存时,mongos 可能误选后者触发全量广播
explain("executionStats") 在分片集群里怎么看才不被误导
默认 explain 只显示单个分片的统计,完全不能代表全链路耗时。
真正要看的是:executionStages.shards 数组里每个分片的 executionTimeMillisEstimate 分布。如果某一分片耗时远高于其他,说明它承担了无效扫描。
务必加 maxTimeMS 测试,否则慢查询可能被 mongos 静默截断,掩盖真实瓶颈。例如:
db.orders.find({region: "cn-east", create_time: {$gt: ISODate("2025-06-01")}}).explain("executionStats").executionStages.shards
看到 stage: "SHARD_MERGE" 是正常现象,但若伴随各分片 docsExamined 差异超过 10 倍,就要检查前缀是否覆盖了高频过滤条件。











