You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何MongoDB查询采用COLLSCAN而非INDEXSCAN?

问题分析与解决方案

问题现象

在taches集合上创建了以下索引:

> db.taches.getIndexes()
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "test.taches"
    },
    {
        "v" : 2,
        "key" : {
            "idProjet" : -1
        },
        "name" : "idProjet_-1",
        "ns" : "test.taches"
    },
    {
        "v" : 2,
        "key" : {
            "id" : -1
        },
        "name" : "id_-1",
        "ns" : "test.taches"
    }
]

但执行以下两个查询时,均触发全集合扫描(COLLSCAN),未使用预期的索引:

查询1:idProjet的$in查询

2023-03-26T15:36:41.691+0000 I  COMMAND  [conn11] command XXXX.taches command: find { find: "taches", filter: { idProjet: { $in: [ 73293, 71255, 71256, 75455, 75454, 75456, 75847, 75846, 75853, 75850, 75851, 75852, 75872, 75871, 75873, 75874, 75880, 75878, 75879, 75881, 76157, 76156, 76158, 76188, 76186, 76189, 76190, 76191, 76477, 76474, 76478, 77021, 77020, 77022, 77023, 77362, 77359, 77360, 77361, 77421, 77419, 77420, 78055, 78052, 78053, 78054, 78090, 78089, 78091, 78794, 78791, 78792, 78793, 79059, 79058, 79063, 79064 ] } }, projection: { id: 1, idProjet: 1, nom: 1, status: 1, ordre: 1 }, $db: "XXXX" } planSummary: COLLSCAN cursorid:7041921742719850182 keysExamined:0 docsExamined:564790 numYields:4412 nreturned:101 queryHash:D536B4A0 planCacheKey:D536B4A0 reslen:10061 locks:{ ReplicationStateTransition: { acquireCount: { w: 4413 } }, Global: { acquireCount: { r: 4413 } }, Database: { acquireCount: { r: 4413 } }, Collection: { acquireCount: { r: 4413 } }, Mutex: { acquireCount: { r: 1 } } } storage:{} protocol:op_query 385ms

查询2:id单值查询

2023-03-26T15:47:56.482+0000 I  COMMAND  [conn11] command XXXX.taches command: find { find: "taches", filter: { id: 939185 }, $db: "XXXX" } planSummary: COLLSCAN keysExamined:0 docsExamined:593553 cursorExhausted:1 numYields:4637 nreturned:1 queryHash:6DAB46EC planCacheKey:6DAB46EC reslen:1207 locks:{ ReplicationStateTransition: { acquireCount: { w: 4638 } }, Global: { acquireCount: { r: 4638 } }, Database: { acquireCount: { r: 4638 } }, Collection: { acquireCount: { r: 4638 } }, Mutex: { acquireCount: { r: 1 } } } storage:{} protocol:op_query 393ms

核心原因排查

1. 索引与查询的数据库不匹配

从索引的ns字段可以看到,你创建的索引属于test.taches集合,但实际查询的是XXXX.taches集合——索引建错了数据库,这是导致查询无法使用索引的最直接原因。

2. 字段数据类型不匹配(次要可能)

如果数据库匹配,需检查文档中idProjet、id字段的类型与查询条件的类型是否一致:

  • 例如文档中idProjet存储为字符串类型,但查询用的是数字;
  • 这种类型不匹配会导致MongoDB无法使用对应索引,只能走全表扫描。

3. 查询计划缓存干扰(次要可能)

MongoDB的查询计划缓存可能保留了旧的全表扫描计划,即使索引已创建,也会继续沿用旧计划。

解决方案

步骤1:在目标数据库创建索引

切换到XXXX数据库,重新创建所需索引:

use XXXX
db.taches.createIndex({idProjet: -1}, {name: "idProjet_-1"})
db.taches.createIndex({id: -1}, {name: "id_-1"})

创建完成后,执行db.taches.getIndexes()确认索引存在,且ns字段为XXXX.taches。

步骤2:验证字段数据类型一致性

检查文档中字段类型:

// 查看idProjet字段的类型分布
db.taches.aggregate([{$group: {_id: {$type: "$idProjet"}, count: {$sum: 1}}}, {$sort: {count: -1}}])
// 查看id字段的类型分布
db.taches.aggregate([{$group: {_id: {$type: "$id"}, count: {$sum: 1}}}, {$sort: {count: -1}}])

如果存在类型不匹配(比如既有数字又有字符串),需统一字段类型,或调整查询条件的类型与文档一致。

步骤3:强制使用索引测试(可选)

若索引已创建但仍不走索引,可强制指定索引验证:

// 强制使用idProjet索引
db.taches.find({idProjet: {$in: [73293, ...]}}).hint("idProjet_-1").explain("executionStats")
// 强制使用id索引
db.taches.find({id: 939185}).hint("id_-1").explain("executionStats")

如果强制后能正常使用索引,说明是查询计划缓存的问题,可清除该查询的计划缓存:

// 清除指定查询的计划缓存
db.taches.getPlanCache().clearPlansByQuery({id: 939185})
db.taches.getPlanCache().clearPlansByQuery({idProjet: {$in: [73293, ...]}})

步骤4:检查索引构建状态

如果索引刚创建不久,需确认索引是否已构建完成:

// 查看当前正在运行的操作,确认索引构建是否完成
db.currentOp({op: "command", "command.createIndexes": {$exists: true}})

若索引仍在构建中,需等待构建完成后再测试查询。

内容的提问来源于stack exchange,提问作者Bastien Bonhoure

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 19:24:58