诊断MongoDB分片集群的查询性能问题,最令人头疼的并非单纯的慢查询,而是“无法定位慢的原因”。广播查询正是这类问题中最隐蔽的隐患——它表面上看似正常执行,实际上已将整个集群的架构压力推至极限。以下排查手段均经过实战反复检验,堪称诊断广播查询的“照妖镜”。
查 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 })
看 mongos 日志里有没有 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 资源,导致后续所有请求排队等待。它不同于普通查询,难以被快速终止,因为归并逻辑在 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() 逐一核对,线上环境常在此环节暴露问题——索引虽然创建,但实际效果如同“假快”。
真正棘手的并非“没有索引”,而是“索引已建,但分片层根本用不上”。广播与归并一旦成为常态,单纯增加硬件、升级配置、调整参数只能暂时缓解,若不重构查询语义或分片键设计,问题只会随数据增长持续恶化。
