分片键需高基数且高频查询,如tenant_id+created_at;索引须分片键前置;聚合$match必须首置;chunk迁移致性能抖动,应低峰手动控制。

分片键选择不当会导致全分片扫描
分片键决定数据如何分布,也直接影响查询能否命中目标分片。如果用 _id(默认 ObjectId)作分片键,虽然写入均匀,但范围查询或正则匹配会触发所有分片参与——哪怕只查 10 条数据,也要在全部 10 个 shard 上执行索引扫描,IO 和网络开销翻倍。
真正有效的分片键需满足两个条件:高基数 + 查询高频字段。比如日志场景中,tenant_id + created_at 组合成复合分片键,既避免单点热点,又能让 { tenant_id: "abc", created_at: { $gte: ISODate("...") } } 直接路由到少数 shard。
- 避免用单调递增字段(如纯时间戳)单独作分片键,否则新数据持续写入最后一个 chunk,引发写入瓶颈
- 哈希分片(
sh.shardCollection("db.col", { field: "hashed" }))适合等值查询,但不支持范围扫描,慎用于时间范围类业务 - 可通过
sh.status()观察 chunk 分布是否倾斜;倾斜超过 20% 就该考虑重新分片或调整分片键
索引必须在每个分片上独立构建
MongoDB 不会自动同步索引,分片集群里每个 shard 是独立的 mongod 实例。如果只在 mongos 上执行 createIndex,它只是下发命令——但某个 shard 若因故障未执行成功,后续对该 shard 的查询就退化为 collection scan。
务必逐个登录 shard 节点,用 db.col.getIndexes() 核验索引是否存在、字段顺序是否一致。尤其注意:复合索引中分片键字段必须是前导字段,否则无法下推过滤条件。
- 错误示例:
db.col.createIndex({ status: 1, timestamp: -1 }),而分片键是{ tenant_id: 1, timestamp: -1 }→ 该索引无法用于{ tenant_id: "x", status: "done" }查询下推 - 正确做法:先确保分片键字段前置,再叠加查询字段,如
{ tenant_id: 1, timestamp: -1, status: 1 } - 大集合建索引建议加
{ background: true },否则阻塞写入;但 background 模式在 4.2+ 版本中仍可能锁住 chunk 迁移
聚合管道中 $match 必须放在最前面才能下推
mongos 只有在聚合第一阶段是 $match 且条件含分片键时,才会把该条件“下推”到各 shard 执行。一旦中间夹了 $lookup 或 $unwind,整个 pipeline 就变成:各 shard 全量返回数据 → mongos 内存中合并处理 → OOM 风险陡增。
典型陷阱是带分页的聚合:有人写成 [{ $skip: 1000 }, { $limit: 50 }, { $match: { ... } }],这等于先拉回 1050 条原始文档再过滤,完全丧失分片意义。
- 正确顺序永远是:
$match(含分片键)→$project/$sort→$skip/$limit - 若需按非分片键排序,
$sort无法下推,此时应预计算并冗余存储排序字段(如把score存进文档),再将其加入分片键或索引前导位 - 用
explain("executionStats")查看shards字段下的totalDocsExamined,若远大于预期结果数,说明下推失败
chunk 迁移期间查询性能抖动不可避免
当某 shard 数据量增长过快,balancer 会自动切分并迁移 chunk。迁移过程本身不阻塞读写,但目标 shard 在接收数据时,磁盘 IO 和内存压力上升,db.currentOp() 中会出现大量 moveChunk 操作,此时并发查询响应延迟可能突增 3–5 倍。
这不是 bug,而是设计权衡。生产环境应主动控制节奏:关闭自动 balancer(sh.setBalancerState(false)),在低峰期手动调用 sh.moveChunk(),并监控 sh.status() 中的 pending 状态。
- 迁移中的 chunk 仍可正常读写,但查询可能短暂返回旧数据(最终一致性)
- 不要在迁移期间执行大范围
updateMany或deleteMany,容易触发 write conflict - chunk 大小默认 64MB,TB 级数据建议调至 128–256MB,减少 chunk 总数和迁移频次,但需确保单个 chunk 能被单节点内存容纳











