只要看到"stage": "SHARD_MERGE"即可断定查询被广播到多个分片并由mongos合并结果,这是架构级开销,需关注nReturned小但totalDocsExamined大、shards数组长度>1且各分片docsExamined非零。
explain("executionStats") 里有没有 SHARD_MERGE
这是最直接的信号:只要看到 "stage": "SHARD_MERGE",基本就能断定查询被广播到了多个分片,再由 mongos 合并结果。这不是“有点慢”,而是架构级开销——每个分片都得扫描、排序、序列化、跨网络传输,mongos 再归并。
重点关注两个字段:
executionStats.nReturned 很小(比如 100),但 totalDocsExamined 是各分片之和(比如每个 shard 扫了 5 万,3 个 shard 就是 15 万)shards 数组长度 > 1,且每个分片的 executionStages 下都有非零的 docsExamined
执行命令示例:db.orders.explain("executionStats").find({ status: "pending" }).sort({ createdAt: -1 })
broadcast: true
日志比 explain 更真实,尤其能暴露毛刺和高频问题。在 mongos 的日志中搜索:"command":"find".*"sort".*"broadcast":true
如果匹配量高,说明大量查询没带分片键,或排序字段不是分片键前缀。注意:shardCount 字段值大于 1 的慢查询记录,也是同源线索。
别只盯平均耗时——广播查询的延迟分布极不均匀,往往取决于最慢那个 shard 的响应时间。
db.currentOp() 抓正在跑的 mongosMerge 操作运行 db.currentOp({ "secs_running": { "$gt": 5 } }),过滤出活跃时间超 5 秒的操作,重点找:"type": "mongosMerge" 或 "desc": "mongos merge"
这类操作一旦堆积,会吃光 mongos 的内存和 CPU,导致后续所有请求排队。它不像普通查询那样容易被 kill,因为归并逻辑在 mongos 进程内完成,不走 shard 的 oplog。
对比验证法很有效:直连某个 shard 执行相同查询(如 mongo --host shard01:27018),如果快 10 倍以上,基本锁定是归并瓶颈。
索引存在 ≠ 能路由。分片集群里,索引必须把分片键字段放在最左边,否则即使有索引,mongos 也无法判断该去哪个 shard 查。
例如分片键是 { tenantId: 1, _id: 1 },那么这些索引有效:
{ tenantId: 1, _id: 1, status: 1 }{ tenantId: 1, status: 1 }(跳过 _id 没关系,只要 tenantId 在最左)而这些索引无效:
{ status: 1, tenantId: 1 }(tenantId 不在最左){ createdAt: 1 }(完全不含分片键)用 db.collection.getIndexes() 逐个核对,线上库常卡在这一步——索引建了,但全是“假快”。
真正难处理的不是“没索引”,而是“索引建了但分片层根本用不上”。广播和归并一旦成为常态,加硬件、升配置、调参数都只是缓释,不重构查询语义或分片键设计,问题只会随数据增长持续恶化。