分片环境文档模型设计需严守三原则:分片键必须参与所有高频查询且不可为数组或嵌套路径;嵌入式结构须规避跨分片事务与文档膨胀;关联查询应优先内嵌,引用时确保被引用集合未分片或分片键一致。

分片环境下的文档模型设计,不是简单把单机模型复制过去就能跑通——分片键选错、嵌套结构不合理、跨分片查询频繁,三者任一出问题,集群吞吐量就断崖式下跌。
分片键必须参与所有高频查询条件
分片键决定数据落在哪个 shard 上,MongoDB 只有在查询中明确包含分片键时,才能路由到目标 shard;否则触发广播查询(all shards),性能归零。
- 错误写法:
db.orders.find({ status: "pending" })—— 没带分片键,全集群扫描 - 正确写法:
db.orders.find({ user_id: "u123", status: "pending" })—— 前置分片键,精准路由 - 分片键字段不能是数组或嵌套路径(如
"address.city"),否则无法哈希或范围分片 - 高基数 + 低写入热点字段优先:比如
user_id比status更适合作为分片键
嵌入式结构要避开跨分片事务和文档膨胀
MongoDB 分片集群中,multi-document transaction 仅支持在单个分片内执行;跨分片事务要么失败,要么被拒绝。同时,文档超过 16MB 或频繁重写导致的文档迁移,会严重拖慢 chunk 迁移速度。
- 订单详情适合嵌入:
items数量稳定(通常 ≤ 50)、总大小可控,且与order_id强绑定 - 用户行为日志不适合嵌入:如果一个用户每天产生数百条日志,
logs: [ ... ]会导致文档持续膨胀,最终触发moveChunk频繁迁移 - 替代方案:用独立集合 + 复合分片键,例如
{ user_id: ..., date: ... },既保证局部性,又避免单文档过大
关联查询必须提前规划引用方式
分片环境下 $lookup 仅支持“左表分片键 = 右表字段”且右表必须是未分片集合(或使用 let + $expr 但性能极差),硬做跨分片关联等于自废武功。
- 能内嵌绝不引用:地址、配置项、状态变更历史等低频更新、小体积数据,直接塞进主文档
- 必须引用时,确保被引用集合不启用分片(如
products表),或让引用字段与主表分片键一致(如订单引用商品,商品按category_id分片,而订单也按category_id分片) - 禁用
DBRef:它不提供任何路由优化,反而增加一次额外查询,且 driver 层不自动解析
索引必须覆盖分片键+查询模式
分片集群里,索引建在非分片键字段上,只在单个 shard 内有效;但若缺失分片键前缀,即使有索引,查询仍需广播。
- 坏索引:
db.orders.createIndex({ created_at: 1 })—— 单独建时间索引,对分片无意义 - 好索引:
db.orders.createIndex({ user_id: 1, created_at: 1 })—— 分片键前置,支持{ user_id: ..., created_at: { $gt: ... } }高效查询 - 排序字段必须进索引:分片环境下
sort()不走索引 = 全 shard 内存排序,极易触发Sort operation used more than the maximum 33554432 bytes of RAM
真正卡住人的从来不是语法,而是“这个字段该不该嵌进去”“那个查询会不会广播”“这次加索引会不会让 chunk 迁移卡死”——这些判断没有银弹,全靠对业务读写比例、数据增长节奏、分片拓扑的实时感知。

















