COLLSCAN 表示全集合扫描,即未使用索引而逐条读取文档;常见原因包括索引顺序不匹配、查询操作不可索引、字段类型不一致或 Plan Cache 未更新。

COLLSCAN 就是全集合扫描,说明没走索引
它不是报错,而是执行计划里 stage 字段值为 COLLSCAN 的明确信号:MongoDB 正在逐条读取整个集合里的文档来匹配查询条件。哪怕只返回 1 条结果,totalDocsExamined 也可能高达几万甚至百万——这正是性能瓶颈的源头。
为什么明明建了索引,explain 还显示 COLLSCAN
常见原因不是索引不存在,而是索引“不匹配”或“不可用”:
- 查询字段和索引字段**顺序不一致**:比如建了复合索引
{status: 1, createdAt: -1},但查询只用了{createdAt: {$gt: ...}},因不满足最左前缀原则,索引被跳过 - 查询中用了无法利用索引的操作:如
{$regex: "^abc"}可能走索引,但{$regex: "abc$"}或{$ne: "x"}基本触发 COLLSCAN - 字段类型不一致:数据库里存的是字符串
"123",查询却传了数字123,类型不匹配导致索引失效 - 索引被“选错”:Plan Cache 复用了旧的、已失效的 winningPlan(尤其在数据分布突变后),
winningPlan.stage显示IXSCAN,但实际执行是COLLSCAN—— 此时要看executionStats.totalDocsExamined是否远大于nReturned
如何快速定位缺失索引并补全
别靠猜,用 explain("executionStats") 和 getIndexes() 对照看:
- 先确认当前查询真实执行路径:
db.collection.find({Tags.SN: "QZ435698245"}).explain("executionStats"),重点盯executionStats.totalDocsExamined和queryPlanner.winningPlan.stage - 查已有索引:
db.collection.getIndexes(),看是否已存在{ "Tags.SN": 1 }单字段索引,或更宽泛的复合索引(如{ "Tags.SN": 1, "OPTime": -1 }) - 如果缺失,立即创建:
db.collection.createIndex({"Tags.SN": 1});若常带排序,优先建覆盖索引:db.collection.createIndex({"Tags.SN": 1, "OPTime": -1}) - 建完立刻验证:再次运行 explain,确认
winningPlan.stage === "IXSCAN"且totalDocsExamined接近nReturned
补索引后仍 COLLSCAN?检查 Plan Cache 是否卡住
索引建了、字段也对了,但执行计划还是不更新——大概率是 Plan Cache 没刷新。这时候不能只清整个集合缓存(clearPlanCache),容易引发编译风暴:
- 先用
db.collection.getPlanCache().list()查出对应查询的queryHash - 再精准清理:
db.runCommand({clearQueryCache: "collectionName", query: {"Tags.SN": "QZ435698245"}, sort: {OPTime: -1}, limit: 20})—— 注意:字段名大小写、嵌套路径(Tags.SN)、操作符(是否显式写$eq)、sort和limit都必须和原始查询**完全一致**,否则匹配失败 - 清理后重跑 explain,观察
executionStats.executionTimeMillis是否明显下降
Plan Cache 的“惰性更新”机制容易被忽略:它不会自动感知数据倾斜或索引新增,必须人工干预才能让新索引真正生效。


















