为何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
相关产品推荐
相关产品推荐

