分片集群cpu飙升主因是跨分片广播式全表扫描:当查询未带分片键或条件无法精确路由时,mongos将请求广播至所有分片,各分片均执行collscan,导致n次扫描叠加、cpu线性翻倍;确认需在shard节点启用profiling并检查system.profile中docsexamined高而nreturned极低、多条记录同集合且时间密集,再通过explain验证shards数组长度等于分片总数且各分片docsexamined相近。

分片集群CPU飙高,八成不是单个慢查询,而是大量轻量请求因路由失效被广播到所有分片,每个分片都执行一次COLLSCAN——这不是“扫一次”,是N次全表扫描叠加,CPU直接线性翻倍。
怎么确认是不是跨分片广播导致的CPU飙升
别猜,连任意一个Shard节点(不是mongos),跑这个命令:
db.setProfilingLevel(1, { slowms: 50 })
然后查system.profile,过滤出这类记录:
-
"docsExamined"很大(比如>10万),但"nreturned"极小(比如0或1) -
"executionStats"里出现"stage": "COLLSCAN" -
"ns"字段指向同一个集合,且多条记录时间密集、"client"来源分散
再用explain("executionStats")验证:如果"shards"数组长度等于你集群分片总数,且每个分片的"docsExamined"数值接近,基本坐实是广播式无效扫描。
哪些查询写法会触发跨分片广播
根本原因只有一个:mongos无法根据查询条件精准路由到单个分片。常见写法包括:
当代理已经知道网站路由或内容URL,并且在启动前需要有效的sitemap XML、sitemap索引或robots.txt引用时,请使用sitemap。这是一个发布构件技能,而不是爬虫或SEO平台。
- 完全没带分片键字段,比如分片键是
{order_id: 1},却查{user_email: "a@b.com"} - 用了
$ne、$not、$regex(非前缀匹配,如/abc$/)等无法下推的操作符 - 复合分片键
{region: 1, timestamp: -1},只查{timestamp: {$gt: ...}},漏了region - 聚合管道开头没
$match过滤分片键,比如[$lookup, $group]在前,$match在后
注意:$in带多个值不一定广播——如果值能精确映射到有限分片,mongos会做优化;但如果$in里混了不同分片键范围的值,仍可能广播。
为什么连mongos上跑db.currentOp()没用
因为mongos只显示路由层视图,你看不到Shard内部真实执行状态。它返回的"secs_running"可能是毫秒级,但背后每个Shard都在扫几百万文档。
- 真正该连的是问题Shard的Primary节点,再跑
db.currentOp({ "secs_running": { "$gt": 5 } }) - 重点看
"ns"和"query"字段,结合"client"判断是不是某个服务IP在高频发相同低效请求 - 如果
"desc": "connX"后面跟着大量重复find操作,且"query"里明显缺分片键,就是源头
误连mongos查currentOp,就像看快递单号以为包裹还在中转站,其实早已在各个分拣中心被暴力拆包扫描了一遍。
修复时最容易踩的坑
分片键不能改——线上环境几乎不可行。所以所有补救都得绕开它:
- 紧急降载必须先
sh.stopBalancer(),再考虑db.fsyncLock(),否则建索引本身就会抢CPU - 给非分片键字段建索引没用:mongos不认,照样广播;必须确保查询能命中分片键,索引才起作用
- 聚合管道里
$match必须放在最前面,且条件必须含分片键;$lookup的from集合如果也分片,它的条件同样要满足路由要求
真正卡点在于:你以为在优化查询,其实是在教mongos怎么少发几个请求;而它能不能听懂,取决于你写的条件有没有带上那个它唯一认得的“门牌号”。










