应先运行db.currentop({"secs_running": {"$gt": 5}})定位高耗时操作,再查system.profile确认collscan,最后用explain分析执行计划并针对性建索引。

查当前正在执行的慢操作:db.currentOp()
Shard节点CPU飙到100%,第一反应不是重启,而是看它到底在忙什么。直接连上问题Shard的Primary节点(别连mongos),运行db.currentOp({ "secs_running": { "$gt": 5 } }),重点关注secs_running高、docsExamined巨大、op为query或update的操作。
常见陷阱:
- 误连mongos执行该命令——它返回的是路由层视图,看不到shard内部真实耗时操作
- 忽略
client字段——可能发现是某个定时任务IP反复发起相同低效查询 - 没加过滤直接跑
db.currentOp()——结果太多,反而掩盖真正长耗时操作
确认是否全表扫描:system.profile里找COLLSCAN
先确保该shard已开启profiling(生产环境推荐设为1):db.setProfilingLevel(1, { slowms: 20 })。然后查最近的慢操作:db.system.profile.find({ "millis": { "$gt": 50 } }).sort({ "ts": -1 }).limit(5)。
关键判断依据:
- 看到
"keysExamined": 0且"docsExamined"远大于返回条数(比如查10条却扫了200万文档),基本锁定COLLSCAN -
"ns"字段指向具体集合,比如"myapp.logs",这就是索引缺失的高危目标 - 如果
"nreturned"为0但docsExamined极大,说明查询条件根本没命中任何数据——索引设计可能完全偏离业务查询模式
验证索引是否存在及是否被命中:db.collection.getIndexes()和explain()
对system.profile中暴露的慢集合,先查已有索引:db.myapp_logs.getIndexes()。再拿典型查询语句做执行计划分析:db.myapp_logs.find({ "user_id": 123, "created_at": { "$gte": ISODate("2026-05-01") } }).explain("executionStats")。
注意几个致命信号:
- 返回中
"stage": "COLLSCAN"——索引压根没被用上 -
"totalDocsExamined"接近集合总文档数,但"totalKeysExamined"为0——索引存在但字段顺序/类型不匹配 - 查询用了
$regex开头或$ne,而索引字段在复合索引中不在最左前缀——前缀匹配失效 - 集合文档量超千万,但唯一索引建在
_id以外的字符串字段上,且该字段重复率高——索引选择性差,优化器主动放弃
紧急降载与索引添加的实操边界
CPU已到100%时,不能直接建索引——后台建索引仍会持续抢CPU资源。必须分两步:
- 先临时降载:在问题shard的Primary上执行
sh.stopBalancer()(如果是分片集群),再用db.fsyncLock()短暂冻结写入(慎用,只限分钟级),快速kill掉已知长耗时op:db.killOp(opid) - 建索引务必加
{ background: true },否则会阻塞所有读写;若集合极大,考虑先在Secondary节点建好再切换Primary,避免主节点雪崩 - 切忌直接操作
config库删chunks——这会导致chunk元数据丢失,出现“数据存在但查不到”的诡异现象,恢复只能靠备份
最易被跳过的细节:片键(shard key)本身必须有索引,且不能是稀疏索引或TTL索引;如果片键字段是字符串但业务本意是时间范围查询,却没建对应前缀的复合索引,均衡过程中的迁移和路由查询照样会触发大量COLLSCAN。











