查询条件未带分片键字段是定向查询失效的最常见原因,mongos无法推导目标分片,只能向所有分片广播请求,即使只查一条记录也需全集群扫描;必须提供分片键完整前缀字段(如{orgid: "o1"})才能定向路由。

查询条件没带分片键字段,mongos 只能广播
这是最常见原因:mongos 根本没法推导目标分片,只能把请求发给所有 shard。哪怕你只查一条记录,也要扫全集群。比如分片键是 {orgId: 1, userId: 1},但查询写成 {userId: "u123"},就必然广播——MongoDB 不支持“仅凭后缀字段路由”。
必须显式提供分片键的完整前缀字段:{orgId: "o1"} 或 {orgId: "o1", userId: "u123"} 才能定向;{userId: "u123", orgId: "o1"} 也行,字段顺序不影响路由,但字段名和存在性必须严格匹配。
验证方式:用 explain("executionStats") 看返回里的 executionStages.shards 数量是否为 1,且只有那个分片的 executionTimeMillisEstimate 显著大于 0。
分片键字段类型不一致,哈希计算错位
即使写了分片键字段,如果类型和实际存储不一致,mongos 就会把请求发到错误分片,甚至多个分片。典型场景是 orgId 字段混存了字符串("o1")和 ObjectId(ObjectId("...")),而你查 {orgId: "o1"},mongos 可能因类型歧义放弃精确路由。
检查真实类型的方法:db.collection.findOne().orgId,再配合 typeof 或 $type 操作符确认;查询时保持类型完全一致——字符串就传字符串,数字就传数字,别依赖驱动自动转换。
聚合中尤其注意:{$match: {orgId: {$eq: "o1"}}} 比 {$match: {orgId: "o1"}} 更明确,能减少驱动层误处理风险。
用了 $ne、$regex 等非路由友好操作符
MongoDB 明确不支持对这些操作符做分片路由推导,哪怕分片键字段出现在条件里,也会强制广播。这不是 bug,是设计限制。
-
$ne、$not、$where全部触发广播 - 非前缀正则不行:
{name: {$regex: "abc"}}或{name: /.*abc/}都不行;只有{name: {$regex: "^abc"}}这类前缀匹配才可能定向 -
$or是隐形陷阱:即使每个分支都含分片键,整个$or表达式也会广播
替代方案:拆成多次定向查询,或把过滤逻辑移到应用层(适合结果集小的场景)。
索引在部分 shard 上缺失或结构不一致
mongos 执行 createIndex() 是“尽力而为”:它向各 shard 发命令,但不等全部成功就返回。网络抖动、shard 临时不可达、权限不足(如缺少 createIndex 权限),都可能导致索引只建在部分节点上。
后果很隐蔽:explain() 看起来正常,但某些 shard 实际走的是 COLLSCAN,因为没索引可用;而 mongos 汇总执行统计时根本不会暴露这个差异。
必须逐个直连每个 shard 的 primary 节点,运行 db.collection.getIndexes(),比对三项关键内容:name(索引名)、key(字段顺序和方向)、unique/sparse/partialFilterExpression 等选项。任何一项不一致,都可能导致查询计划错乱。











