复合分片键中company_id不能放前面哈希,因其基数低导致数据倾斜,且后续createTime字段使范围查询需广播至全部分片;设计有效性取决于真实查询模式而非字段基数。

复合分片键里为什么不能把 company_id 放前面再哈希?
因为 company_id 在 SaaS 场景下往往基数极低(比如只有几十或几百个租户),即使加了 "hashed",哈希后也还是最多落到同样数量的 chunk 上——数据照样堆在少数分片。更关键的是,create_time: 1 跟在哈希字段后面,就彻底丧失范围查询能力:所有 {$gt: ISODate("2026-01-01")} 查询都会广播到全部分片。
实操建议:
- 先验证基数:
db.visitor.distinct("company_id").length,若结果 - 跑分布聚合:
db.visitor.aggregate([{$group: {_id: "$company_id", count: {$sum: 1}}}, {$sort: {count: -1}}, {$limit: 10}]),前 10 项占比超 30% 就是强信号 - 别建
{"company_id": "hashed"}和{"company_id": 1}两个索引,mongos可能误选后者触发全量广播
什么样的复合分片键结构才真正有效?
必须满足两个硬条件:前缀字段支持高频路由 + 范围扫描,后置字段提供高区分度打散能力。例如 {region: 1, user_id: "hashed"} 中,region 是业务真实存在的非单调字段(如“华北”“华东”),能将 80% 查询限制在 1–2 个分片;user_id 是 UUID 或雪花 ID,哈希后确保每个 region 内写入均匀。
关键约束:
-
region不能是_id、时间戳或自增整数——这些天然单调,会制造 Jumbo Chunk -
user_id必须是单值、非数组、非浮点(或确保 ≤2⁵³),否则哈希行为不可控 - 整个分片键字段必须在所有文档中存在且类型一致,否则
sh.shardCollection()会报cannot shard collection with missing or inconsistent shard key value
怎么用 analyzeShardKey 验证设计是否靠谱?
别靠经验猜。MongoDB 7.0 的 analyzeShardKey 是基于真实采样做的分布模拟,它不看 schema,只看数据和查询模式。
执行示例:db.visitor.analyzeShardKey({region: 1, user_id: "hashed"}, {sampleSize: 100000})
重点关注返回字段:
-
imbalancedChunks> 5%:说明即使加了哈希,数据仍倾斜——大概率是前缀字段region分布本身就不均(比如 90% 流量都在“华东”) -
broadcastOperations值高:说明你设计的前缀没覆盖常见查询条件(比如漏了region过滤,导致所有查询都带user_id单独查) -
estimatedChunkCount> 1000:前缀粒度太细(如用了region+device_type+os_version),chunk 数爆炸会影响迁移效率
为什么 explain("executionStats") 在分片集群里容易误判?
默认 explain 只显示单个分片的统计,不是全集群耗时。真正要盯的是 executionStages.shards 下每个分片的 executionTimeMillisEstimate 分布。
典型陷阱:
- 某一分片耗时明显高于其他(比如 1200ms vs 其余均 ≤200ms):说明该分片存在热点,或局部缺失索引
- 出现
stage: "SHARD_MERGE"是正常现象,但若各分片的docsExamined差异巨大(如一个扫 10 万,其余扫 100),说明部分分片做了无效扫描 - 务必加
maxTimeMS测试,否则慢查询可能被mongos静默截断,掩盖真实瓶颈
最易被忽略的一点:复合分片键的设计有效性,永远取决于你的**真实查询模式**,而不是字段看起来“应该”能打散。哪怕 user_id 基数再高,如果 95% 查询都不带 user_id,那它放在复合键里就是摆设。


















