MongoDB分片路由失败主因有三:分片键前缀缺失、不支持的操作符(如$ne、$lookup)、分片键字段类型不一致;需用explain和getShardDistribution验证真实路由,避免静默广播。

查询没带分片键完整前缀
mongos 路由时,必须能从查询条件里推导出目标分片。如果只传了 {userId: "u123"},但分片键是 {"region": 1, "userId": 1},那它无法确定该去哪个分片——因为 userId 不是前缀字段,region 缺失,路由失效,只能广播。
常见场景包括:
- 前端调用漏传
region字段,后端没校验就直接查 - 聚合管道中
$lookup后丢失了region,后续$match只剩userId - 旧文档缺失
region字段(值为null或根本不存在),导致该文档无法被任何 chunk 覆盖,mongos 放弃精准路由
用了不支持路由的查询操作符
MongoDB 的路由逻辑对操作符很敏感。只要查询里出现 $ne、$not、$regex(非 ^ 开头的前缀匹配)、$where、$text,或者聚合里有 $facet、$lookup(没对齐分片逻辑)、$unwind(破坏分片键上下文),mongos 就会放弃 Targeted Query,退回到广播模式。
例如:
-
db.users.find({region: "cn", userId: {$ne: "u123"}})→ 广播,哪怕region和userId都在分片键里 -
db.orders.aggregate([{$lookup: {from: "users", localField: "userId", foreignField: "_id"}}, {$match: {region: "cn"}}])→ 广播,因为$lookup引入了跨集合关联,且users集合未按相同逻辑分片
分片键字段类型不一致
分片键值参与哈希或范围计算,类型必须严格一致。如果 region 字段有的存 "cn"(string),有的存 ObjectId("..."),mongos 对两者哈希结果完全不同,自然无法映射到同一 chunk,路由失败,触发广播。
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
这种问题常出现在:
- 历史数据迁移时字段类型未统一清洗
- 不同服务写入逻辑不一致(如 A 服务写 string,B 服务误写 ObjectId)
- 使用默认值或空值填充时类型隐式转换(比如
undefined→null→ 在 BSON 中类型不同)
检查方式:用 db.collection.distinct("region", {}, {readConcern: "majority"}) 看实际类型分布,再结合 typeof 或 $type 过滤验证。
误判“合理广播”和“危险广播”
不是所有广播都错。像 db.users.countDocuments({})、db.users.find({email: "a@b.com"}) 这类本就不含分片键的查询,广播是预期行为。真正危险的是:你以为查的是单分片,explain 却显示 nShards > 1 且多个分片的 executionTimeMillisEstimate 都非零——说明你写的条件本应命中单分片,但实际没生效。
最容易被忽略的点:
-
sh.status()显示 chunks 已分配,但db.users.getShardDistribution()返回只有一行 → 数据根本没分出去,全在 primary shard,此时任何“分片查询”都是假象 - 刚插入数据还没触发 chunk 拆分,
getShardDistribution()滞后,不能立刻断定分片失败 - C# Driver 的
.Find()不报错也不预警,静默广播;必须主动加.Explain()才能看到真实路由路径

















