必须在查询条件中显式包含分片键字段,否则mongos无法定位目标分片,只能广播到所有分片执行Scatter-Gather;哈希分片仅支持等值查询路由,范围查询会退化为全分片扫描,复合分片键查询须带前缀字段才能精准路由。

必须在查询条件中显式包含分片键字段,否则 mongos 会广播到所有分片——这是 Scatter-Gather 的唯一根源。
为什么查询不带分片键就一定会变 Scatter-Gather?
mongos 路由时依赖分片键值查 Catalog Cache,确定目标分片。一旦查询条件里没出现分片键字段(哪怕只少一个),它就无法定位数据归属,只能把请求发给每个分片,再合并结果。
- 错误示例:
db.orders.find({status: "paid"})—— 如果分片键是{user_id: "hashed"},这个查询必然全量广播 - 正确写法:
db.orders.find({user_id: ObjectId("..."), status: "paid"}),哪怕status没建索引,也能精准路由到 1 个分片 - 复合分片键同理:
db.logs.find({region: "us", level: "error"})只有带上region才能避免广播;level单独查就是全量扫描
哈希分片键下做范围查询的陷阱
用 user_id 哈希分片后,{user_id: {$gt: "abc"}} 这类范围条件会失效:哈希值打散后,范围不再对应连续 chunk,mongos 无法推断目标分片,退化为 Scatter-Gather。
- 哈希分片只支持等值查询路由:
{user_id: ObjectId("...")}或{user_id: {"$in": [...]}} - 想支持时间范围查询,得用升序/降序分片键,比如
{created_at: -1},但要警惕热点问题 - 混合使用时注意:如果分片键是
{user_id: "hashed", created_at: -1},查询必须带user_id等值条件,created_at才能用于索引过滤
聚合管道里漏掉分片键的典型场景
聚合阶段如 $match、$lookup 或 $facet 中只要有一个阶段没包含分片键,整个 pipeline 就可能被广播——尤其是跨集合 $lookup 时,目标集合若未分片或分片键不匹配,mongos 会 fallback 到全分片拉取。
- 检查方式:
db.orders.explain("executionStats").aggregate([...]),看输出里nShards是否等于实际分片数 - 常见翻车点:
$lookup目标表是users,但users的分片键是email,而 pipeline 里只传了user_id,导致无法路由 - 补救办法:要么在
$lookup条件中补上目标集合的分片键字段,要么确保被关联集合也按相同逻辑分片
最容易被忽略的是:应用层 ORM(比如 Laravel-mongodb)生成的查询可能自动省略分片键字段,尤其在动态构造 where 条件时——别信框架默认行为,每次上线前用 explain 验证真实路由路径。











