2. 分片键设计核心原则

分片键(Shard Key)是 MongoDB 分片集群中决定数据分布与查询路由的核心字段组合。它一旦选定,后续修改成本极高,因此设计阶段必须从五个维度系统评估:基数(Cardinality)、频率(Frequency)、单调性(Monotonicity)、读写模式匹配(Query & Write Pattern Fit)、不可变性(Immutability)。这五个维度并非孤立指标,而是相互制约的权衡体系。

一、基数:决定可切分的“粒度上限”

基数指分片键可能取值的数量。基数越高,chunk 越容易均匀切分到多个分片;基数过低(如布尔值、性别、状态枚举)会导致大量文档挤在少数取值上,无法进一步分裂。例如以 { status: 1 } 作为分片键,若 90% 文档 status 为 "active",则无论集群有多少分片,这部分数据只能落在同一个 chunk 上,形成不可分裂的热点。

判断标准:分片键的取值数量应远大于分片数量,且分布尽量均匀。经验上,基数至少应达到分片数的数十倍以上。但高基数并不等于好分片键——若某个高频取值占比过大,仍会形成热点,这就引出频率维度。

二、频率:识别“高频取值”带来的倾斜

频率指各取值在文档中出现的比例。理想情况下,每个取值对应的文档数接近。若某取值占比过高,即使基数很高,该取值对应的 chunk 也会成为写入与查询热点。例如以 { tenantId: 1 } 分片,若某大客户占 60% 数据量,则其所在分片必然过载。

修正思路是引入复合分片键,将高频字段与高基数、低相关性的字段组合,例如 { tenantId: 1, orderId: 1 },使同一租户的数据仍能按 orderId 继续切分。

三、单调性:避免“永远写最后一个 chunk”

单调递增或递减的字段(如 ObjectId、时间戳、自增 ID)作为分片键时,所有新写入都会落在最大或最小 chunk 上,形成写入热点。这是最典型的分片键反模式之一。MongoDB 的 hashed 分片策略正是为解决单调性问题而设计:对分片键做哈希后分布,使写入均匀散开,代价是范围查询需要广播到所有分片。

四、读写模式匹配:让查询“定向”而非“广播”

分片键必须能支撑最频繁的查询模式。若查询条件包含分片键,mongos 可直接路由到目标分片(targeted query);否则需广播到所有分片(scatter-gather),延迟与资源消耗显著上升。设计时应统计高频查询的过滤字段,优先选择能覆盖这些字段的分片键,或使用复合分片键的前缀匹配。

五、不可变性:写入后不可更改的硬约束

MongoDB 要求分片键字段值在文档写入后不可修改(除非使用 4.4+ 的 refineCollectionShardKey 调整键结构,但字段值本身仍不可变)。因此分片键应选择业务上天然稳定的字段,避免使用会随业务变化的属性(如用户等级、订单状态)。

分片键五维评估与反模式修正路径 基数 取值数量 ≥ 分片数×N 频率 各取值文档数均衡 单调性 避免递增/递减热点 读写模式匹配 查询可定向路由 不可变性 写入后值不可改 常见反模式 低基数键 { status: 1 } / { gender: 1 } → chunk 无法分裂,热点集中 单调递增键 { _id: 1 } / { createdAt: 1 } → 写入永远落在最后一个 chunk 高频取值键 { tenantId: 1 } 大客户占 60% → 单分片过载,其他分片空闲 修正方案 复合分片键 { tenantId: 1, orderId: 1 } 高频字段 + 高基数字段组合 Hashed 分片策略 sh.shardCollection(..., { _id: "hashed" }) 写入均匀散开,范围查询需广播 复合键 + 范围分片 { tenantId: 1, createdAt: 1 } 租户内按时间范围切分 核心权衡:高基数 + 低频率 + 非单调 + 匹配查询 + 不可变 → 均匀分布 + 定向路由

六、常见反模式与修正方法

反模式一:低基数键。如 { status: 1 }、{ gender: 1 }。修正:改用高基数复合键,或使用 hashed 策略。

反模式二:单调递增键。如 { _id: 1 }、{ createdAt: 1 }。修正:使用 hashed 分片键,或将单调字段与高基数随机字段组合,如 { userId: 1, createdAt: 1 }。

反模式三:高频取值键。如 { tenantId: 1 } 且某租户占绝对多数。修正:复合分片键 { tenantId: 1, orderId: 1 },使同一租户数据继续切分。

反模式四:与查询模式不匹配。如按 { userId: 1 } 分片,但高频查询按 { orderId: 1 }。修正:重新评估查询模式,选择覆盖高频查询的字段,或建立复合索引配合分片键前缀。

七、设计检查清单

维度检查问题合格标准
基数取值数量是否足够多?≥ 分片数 × 50
频率是否存在占比 > 30% 的取值?各取值文档数接近
单调性是否随时间递增/递减?非单调或使用 hashed
读写匹配高频查询是否包含分片键?可定向路由,避免广播
不可变性字段值写入后是否稳定?业务上天然不可变
实践建议:在正式分片前,先用 db.collection.aggregate([{ $group: { _id: "$候选字段", count: { $sum: 1 } } }, { $sort: { count: -1 } }]) 统计候选字段的取值分布,验证基数与频率是否满足要求。对于写入密集型场景,优先考虑 hashed 分片键;对于范围查询密集型场景,优先考虑复合分片键并确保查询前缀匹配。
注意:分片键一旦设定,集合数据将按该键分布。虽然 MongoDB 4.4+ 支持 refineCollectionShardKey 添加后缀字段,但无法更改已有字段值,也无法降低基数或消除已有热点。设计阶段的验证成本远低于后期迁移成本。