带过滤器的Mongo日期范围索引



我们有以下查询

db.Comment.find(
{
$and: [ 
{ reportCount: { $gt: 0 } },
{ assignee: { $exists: false } }, 
{ creationDate: { $gt: new Date(1507831097809) } },
{ creationDate: { $lt: new Date(1508522297966) } },  
{ siteId: 'MAIN' }, 
{ parent: { $exists: false } }, 
{ status: 'ACTIVE' }
]
})
.sort({ creationDate: 1 })

我们有一个索引

{
"v" : 2,
"key" : {
"creationDate" : 1,
"reportCount" : 1,
"label" : 1
}
}

以下是explain结果:

{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "myNameSpace",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [ 
{
"siteId" : {
"$eq" : "MAIN"
}
}, 
{
"status" : {
"$eq" : "ACTIVE"
}
}, 
{
"creationDate" : {
"$lt" : ISODate("2017-10-20T17:58:17.966Z")
}
}, 
{
"creationDate" : {
"$gt" : ISODate("2017-10-12T17:58:17.809Z")
}
}, 
{
"reportCount" : {
"$gt" : 0.0
}
}, 
{
"$nor" : [ 
{
"assignee" : {
"$exists" : true
}
}
]
}, 
{
"$nor" : [ 
{
"parent" : {
"$exists" : true
}
}
]
}
]
},
"winningPlan" : {
"stage" : "FETCH",
"filter" : {
"$and" : [ 
{
"siteId" : {
"$eq" : "MAIN"
}
}, 
{
"status" : {
"$eq" : "ACTIVE"
}
}, 
{
"$nor" : [ 
{
"assignee" : {
"$exists" : true
}
}
]
}, 
{
"$nor" : [ 
{
"parent" : {
"$exists" : true
}
}
]
}
]
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"creationDate" : 1.0,
"reportCount" : 1.0,
"label" : 1.0
},
"indexName" : "creationDate_1_reportCount_1_label_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"creationDate" : [],
"reportCount" : [],
"label" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"creationDate" : [ 
"(new Date(1507831097809), new Date(1508522297966))"
],
"reportCount" : [ 
"(0.0, inf.0]"
],
"label" : [ 
"[MinKey, MaxKey]"
]
}
}
},
"rejectedPlans" : [ 
{
"stage" : "SORT",
"sortPattern" : {
"creationDate" : 1.0
},
"inputStage" : {
"stage" : "SORT_KEY_GENERATOR",
"inputStage" : {
"stage" : "FETCH",
"filter" : {
"$and" : [ 
{
"$nor" : [ 
{
"parent" : {
"$exists" : true
}
}
]
}, 
{
"siteId" : {
"$eq" : "MAIN"
}
}, 
{
"status" : {
"$eq" : "ACTIVE"
}
}, 
{
"creationDate" : {
"$lt" : ISODate("2017-10-20T17:58:17.966Z")
}
}, 
{
"creationDate" : {
"$gt" : ISODate("2017-10-12T17:58:17.809Z")
}
}, 
{
"reportCount" : {
"$gt" : 0.0
}
}, 
{
"$nor" : [ 
{
"assignee" : {
"$exists" : true
}
}
]
}
]
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"parent" : 1.0
},
"indexName" : "parent_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"parent" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"parent" : [ 
"[null, null]"
]
}
}
}
}
}, 
{
"stage" : "SORT",
"sortPattern" : {
"creationDate" : 1.0
},
"inputStage" : {
"stage" : "SORT_KEY_GENERATOR",
"inputStage" : {
"stage" : "FETCH",
"filter" : {
"$and" : [ 
{
"$nor" : [ 
{
"assignee" : {
"$exists" : true
}
}
]
}, 
{
"siteId" : {
"$eq" : "MAIN"
}
}, 
{
"status" : {
"$eq" : "ACTIVE"
}
}, 
{
"creationDate" : {
"$lt" : ISODate("2017-10-20T17:58:17.966Z")
}
}, 
{
"creationDate" : {
"$gt" : ISODate("2017-10-12T17:58:17.809Z")
}
}, 
{
"reportCount" : {
"$gt" : 0.0
}
}, 
{
"$nor" : [ 
{
"parent" : {
"$exists" : true
}
}
]
}
]
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"assignee" : 1.0
},
"indexName" : "assignee_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"assignee" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"assignee" : [ 
"[null, null]"
]
}
}
}
}
}, 
{
"stage" : "SORT",
"sortPattern" : {
"creationDate" : 1.0
},
"inputStage" : {
"stage" : "SORT_KEY_GENERATOR",
"inputStage" : {
"stage" : "FETCH",
"filter" : {
"$and" : [ 
{
"status" : {
"$eq" : "ACTIVE"
}
}, 
{
"creationDate" : {
"$lt" : ISODate("2017-10-20T17:58:17.966Z")
}
}, 
{
"creationDate" : {
"$gt" : ISODate("2017-10-12T17:58:17.809Z")
}
}, 
{
"reportCount" : {
"$gt" : 0.0
}
}, 
{
"$nor" : [ 
{
"assignee" : {
"$exists" : true
}
}
]
}, 
{
"$nor" : [ 
{
"parent" : {
"$exists" : true
}
}
]
}
]
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"siteId" : 1.0,
"updatedDate" : 1.0,
"label" : 1.0
},
"indexName" : "siteId_1_updatedDate_1_label_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"siteId" : [],
"updatedDate" : [],
"label" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"siteId" : [ 
"["MAIN", "MAIN"]"
],
"updatedDate" : [ 
"[MinKey, MaxKey]"
],
"label" : [ 
"[MinKey, MaxKey]"
]
}
}
}
}
}, 
{
"stage" : "SORT",
"sortPattern" : {
"creationDate" : 1.0
},
"inputStage" : {
"stage" : "SORT_KEY_GENERATOR",
"inputStage" : {
"stage" : "FETCH",
"filter" : {
"$and" : [ 
{
"$nor" : [ 
{
"parent" : {
"$exists" : true
}
}
]
}, 
{
"$nor" : [ 
{
"assignee" : {
"$exists" : true
}
}
]
}, 
{
"siteId" : {
"$eq" : "MAIN"
}
}, 
{
"status" : {
"$eq" : "ACTIVE"
}
}, 
{
"creationDate" : {
"$lt" : ISODate("2017-10-20T17:58:17.966Z")
}
}, 
{
"creationDate" : {
"$gt" : ISODate("2017-10-12T17:58:17.809Z")
}
}, 
{
"reportCount" : {
"$gt" : 0.0
}
}
]
},
"inputStage" : {
"stage" : "AND_SORTED",
"inputStages" : [ 
{
"stage" : "IXSCAN",
"keyPattern" : {
"parent" : 1.0
},
"indexName" : "parent_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"parent" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"parent" : [ 
"[null, null]"
]
}
}, 
{
"stage" : "IXSCAN",
"keyPattern" : {
"assignee" : 1.0
},
"indexName" : "assignee_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"assignee" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"assignee" : [ 
"[null, null]"
]
}
}
]
}
}
}
}
]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 19,
"executionTimeMillis" : 8,
"totalKeysExamined" : 533,
"totalDocsExamined" : 56,
"executionStages" : {
"stage" : "FETCH",
"filter" : {
"$and" : [ 
{
"siteId" : {
"$eq" : "MAIN"
}
}, 
{
"status" : {
"$eq" : "ACTIVE"
}
}, 
{
"$nor" : [ 
{
"assignee" : {
"$exists" : true
}
}
]
}, 
{
"$nor" : [ 
{
"parent" : {
"$exists" : true
}
}
]
}
]
},
"nReturned" : 19,
"executionTimeMillisEstimate" : 0,
"works" : 534,
"advanced" : 19,
"needTime" : 513,
"needYield" : 0,
"saveState" : 20,
"restoreState" : 20,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 56,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 56,
"executionTimeMillisEstimate" : 0,
"works" : 533,
"advanced" : 56,
"needTime" : 476,
"needYield" : 0,
"saveState" : 20,
"restoreState" : 20,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"creationDate" : 1.0,
"reportCount" : 1.0,
"label" : 1.0
},
"indexName" : "creationDate_1_reportCount_1_label_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"creationDate" : [],
"reportCount" : [],
"label" : []
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"creationDate" : [ 
"(new Date(1507831097809), new Date(1508522297966))"
],
"reportCount" : [ 
"(0.0, inf.0]"
],
"label" : [ 
"[MinKey, MaxKey]"
]
},
"keysExamined" : 533,
"seeks" : 477,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
},
"ok" : 1.0
}

查询仍然需要700-800毫秒才能返回数据。如何更改索引以使查询运行得更快?不要考虑"keysExamined" : 533, "seeks" : 477,这个数据。这只是测试数据。

看起来它使用了索引,但只使用了索引中的第一个字段?multuKey也是假的?

解释计划输出的几个关键点:

  • 查询处理以下属性:siteId, status, creationDate, reportCount, assignee, parent
  • 获胜计划分为两个阶段:
    • IX_SCAN使用creationDate_1_reportCount_1_label_1,这使用对creationDatereportCount的索引查找来识别56个文档,然后将这些文档转发到FETCH阶段
    • FETCH从IX_SCAN阶段接收56个文档,然后询问这些文档以应用siteIdstatusassigneeparent滤波器。该询问导致37份文件被丢弃,导致19份文件被退回

因此,您的索引仅涵盖查询中6个属性中的2个,而查询中其余的4个属性是通过检查文档而不是索引来应用的。如果您希望此查询完全包含索引,则创建以下索引:

db.collection.createIndex(
{siteId: 1, status: 1, creationDate: 1, reportCount: 1, assignee: 1, parent: 1}
) 

如果使用该索引重新运行,那么您应该会发现(a)MongoDB选择了该索引,(b)IX_SCAN阶段转发的文档数量与find调用返回的文档数量相同。

我说"应该找到">,因为这里还有其他方面可能导致MongoDB选择不同的索引,例如使用$nor和排序阶段(creationDate: 1)。我建议调整索引,每次调整后运行explain"on",并在executionStats子文档中查找这些关键项目:

  • "已返回">
  • "totalKeysExamined">
  • "已检查的totalDocs">

一个简单的经验法则是:totalKeysExaminednReturned越近,totalDocsExamined离零越近。。。你的指数覆盖率就越好。

还有一个问题是索引的成本(就对写入时间和索引存储的影响而言),所以我建议考虑一下您的非功能性需求——在没有完全索引覆盖的情况下,是否可以实现所需的运行时间?如果不是,那么你应该进行经验测试,但要准备好根据explain()输出告诉你的内容来调整你的选择。

最新更新