tl;博士
- 我已经设置了可用作我的查询的索引交集的索引
- 由于数据不足,查询规划器不喜欢交叉点
- 现在我需要一种稳定的方法来验证这个潜在交叉路口计划的正确性,就像使用
explain()
hint( )
用于单个索引设置
我完全理解为什么索引交集不是首选 atm,而且在大多数情况下实际上可能不是首选。但我只是在寻找一种方法来总体上验证索引交集。
========
我有一个 notifications
集合,其中包含诸如
{
"_id": ObjectId("5cdd1819c1136c394a052aa2"),
"notifiable": DBRef("users", ObjectId("5cdd172ac1136c1bdc06bdf2")),
"read_at": ISODate("2019-05-16T07:59:17.985Z")
}
...它有以下索引:
[
{
"v" : 2,
"key" : {
"notifiable.$id" : 1,
"notifiable.$ref" : 1,
"created_at" : -1,
"updated_at" : -1
},
"name" : "notifiable.$id_1_notifiable.$ref_1_created_at_-1_updated_at_-1",
"ns" : "example.notifications"
},
{
"v" : 2,
"key" : {
"read_at" : 1
},
"name" : "read_at_1",
"ns" : "example.notifications"
}
]
当我运行查询时,例如
db.notifications.find({ read_at: { $gt: ISODate("2019-05-16T07:55:57.799Z") }, "notifiable.$id": ObjectId("5cdd172ac1136c1bdc06bdf2") })
...我希望 MongoDB 在需要时使用这两个索引的交集。但是自MongoDB takes a lot of factors into consideration to determine whether an intersection of indices should be used ,我的查询只使用了 1 个索引(可能是因为集合中只有几个文档),甚至 explain(true)
的结果也没有任何 AND_SORTED
AND_HASH
阶段:
{
"queryPlanner": {
"plannerVersion": 1,
"namespace": "example.notifications",
"indexFilterSet": false,
"parsedQuery": {
"$and": [{
"notifiable.$id": {
"$eq": ObjectId("5cdd172ac1136c1bdc06bdf2")
}
},
{
"read_at": {
"$gt": ISODate("2019-05-16T07:55:57.799Z")
}
}
]
},
"winningPlan": {
"stage": "FETCH",
"filter": {
"notifiable.$id": {
"$eq": ObjectId("5cdd172ac1136c1bdc06bdf2")
}
},
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"read_at": 1
},
"indexName": "read_at_1",
"isMultiKey": false,
"multiKeyPaths": {
"read_at": []
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": 2,
"direction": "forward",
"indexBounds": {
"read_at": [
"(new Date(1557993357799), new Date(9223372036854775807)]"
]
}
}
},
"rejectedPlans": [{
"stage": "FETCH",
"filter": {
"read_at": {
"$gt": ISODate("2019-05-16T07:55:57.799Z")
}
},
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"notifiable.$id": 1,
"notifiable.$ref": 1,
"created_at": -1,
"updated_at": -1
},
"indexName": "notifiable.$id_1_notifiable.$ref_1_created_at_-1_updated_at_-1",
"isMultiKey": false,
"multiKeyPaths": {
"notifiable.$id": [],
"notifiable.$ref": [],
"created_at": [],
"updated_at": []
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": 2,
"direction": "forward",
"indexBounds": {
"notifiable.$id": [
"[ObjectId('5cdd172ac1136c1bdc06bdf2'), ObjectId('5cdd172ac1136c1bdc06bdf2')]"
],
"notifiable.$ref": [
"[MinKey, MaxKey]"
],
"created_at": [
"[MaxKey, MinKey]"
],
"updated_at": [
"[MaxKey, MinKey]"
]
}
}
}]
},
"executionStats": {
"executionSuccess": true,
"nReturned": 1,
"executionTimeMillis": 0,
"totalKeysExamined": 2,
"totalDocsExamined": 2,
"executionStages": {
"stage": "FETCH",
"filter": {
"notifiable.$id": {
"$eq": ObjectId("5cdd172ac1136c1bdc06bdf2")
}
},
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"works": 4,
"advanced": 1,
"needTime": 1,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"docsExamined": 2,
"alreadyHasObj": 0,
"inputStage": {
"stage": "IXSCAN",
"nReturned": 2,
"executionTimeMillisEstimate": 0,
"works": 3,
"advanced": 2,
"needTime": 0,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"keyPattern": {
"read_at": 1
},
"indexName": "read_at_1",
"isMultiKey": false,
"multiKeyPaths": {
"read_at": []
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": 2,
"direction": "forward",
"indexBounds": {
"read_at": [
"(new Date(1557993357799), new Date(9223372036854775807)]"
]
},
"keysExamined": 2,
"seeks": 1,
"dupsTested": 0,
"dupsDropped": 0,
"seenInvalidated": 0
}
},
"allPlansExecution": [{
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"totalKeysExamined": 2,
"totalDocsExamined": 2,
"executionStages": {
"stage": "FETCH",
"filter": {
"notifiable.$id": {
"$eq": ObjectId("5cdd172ac1136c1bdc06bdf2")
}
},
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"works": 3,
"advanced": 1,
"needTime": 1,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"docsExamined": 2,
"alreadyHasObj": 0,
"inputStage": {
"stage": "IXSCAN",
"nReturned": 2,
"executionTimeMillisEstimate": 0,
"works": 3,
"advanced": 2,
"needTime": 0,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"keyPattern": {
"read_at": 1
},
"indexName": "read_at_1",
"isMultiKey": false,
"multiKeyPaths": {
"read_at": []
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": 2,
"direction": "forward",
"indexBounds": {
"read_at": [
"(new Date(1557993357799), new Date(9223372036854775807)]"
]
},
"keysExamined": 2,
"seeks": 1,
"dupsTested": 0,
"dupsDropped": 0,
"seenInvalidated": 0
}
}
},
{
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"totalKeysExamined": 2,
"totalDocsExamined": 2,
"executionStages": {
"stage": "FETCH",
"filter": {
"read_at": {
"$gt": ISODate("2019-05-16T07:55:57.799Z")
}
},
"nReturned": 1,
"executionTimeMillisEstimate": 0,
"works": 3,
"advanced": 1,
"needTime": 1,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"docsExamined": 2,
"alreadyHasObj": 0,
"inputStage": {
"stage": "IXSCAN",
"nReturned": 2,
"executionTimeMillisEstimate": 0,
"works": 3,
"advanced": 2,
"needTime": 0,
"needYield": 0,
"saveState": 0,
"restoreState": 0,
"isEOF": 1,
"invalidates": 0,
"keyPattern": {
"notifiable.$id": 1,
"notifiable.$ref": 1,
"created_at": -1,
"updated_at": -1
},
"indexName": "notifiable.$id_1_notifiable.$ref_1_created_at_-1_updated_at_-1",
"isMultiKey": false,
"multiKeyPaths": {
"notifiable.$id": [],
"notifiable.$ref": [],
"created_at": [],
"updated_at": []
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": 2,
"direction": "forward",
"indexBounds": {
"notifiable.$id": [
"[ObjectId('5cdd172ac1136c1bdc06bdf2'), ObjectId('5cdd172ac1136c1bdc06bdf2')]"
],
"notifiable.$ref": [
"[MinKey, MaxKey]"
],
"created_at": [
"[MaxKey, MinKey]"
],
"updated_at": [
"[MaxKey, MinKey]"
]
},
"keysExamined": 2,
"seeks": 1,
"dupsTested": 0,
"dupsDropped": 0,
"seenInvalidated": 0
}
}
}
]
},
"ok": 1,
"operationTime": Timestamp(1557996666, 2),
"$clusterTime": {
"clusterTime": Timestamp(1557996666, 2),
"signature": {
"hash": BinData(0, "hDKqoIo9DL71/n8vfgSDS3czZ9c="),
"keyId": NumberLong("6685056801689305089")
}
}
}
我尝试了 https://docs.mongodb.com/v4.0/core/index-intersection/ 中的 orders
示例并得到相同的结果。因为MongoDB currently doesn't allow using multiple indices as a hint ,我不能强制 MongoDB 在我的查询中使用这两个索引。更糟糕的是,以前 explain(true)
似乎总是包含类似 "cursor": "Complex Plan"
的东西,当有可用的索引交叉计划时(Why doesn't MongoDB use index intersection? ) 但现在此信息已被删除。
那么,当可能有足够的数据时,我怎么知道是否会使用索引的交集?
PS:我正在使用运行 MongoDB 4.0.9 Enterprise atm 的 MongoDB Atlas。
最佳答案
如果您查看解释执行统计输出,您会发现它使用的索引非常有效——在这里使用复合索引而不是索引交集似乎是查询计划器的一个不错的决定:
"executionStats": {
"executionSuccess": true,
"nReturned": 1,
"executionTimeMillis": 0,
"totalKeysExamined": 2,
"totalDocsExamined": 2,
...
}
当绝对没有更好的选择时,会使用索引交集,大部分数据都在磁盘上,并且您没有任何远程选择性的索引。
如果您想在本地查看,您可以创建集合,其中 {a: 1}
和 {b: 1}
不是很有选择性,并且您可以看到 mongo 考虑并拒绝使用这些作为索引交集。
const toInsert = [];
for (let i = 0; i < 10000; i++) { toInsert.push({a: i % 10, b: i % 9 }); }
db.test_coll.createIndex({a: 1});
db.test_coll.createIndex({b: 1});
db.test_coll.find({a: 100, b: 100}).explain()
查看被拒绝的计划:
...
{
"stage" : "FETCH",
"filter" : {
"$and" : [
{
"b" : {
"$eq" : 100
}
},
{
"a" : {
"$eq" : 100
}
}
]
},
"inputStage" : {
"stage" : "AND_SORTED",
"inputStages" : [
{
"stage" : "IXSCAN",
"keyPattern" : {
"b" : 1
},
"indexName" : "b_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"b" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"b" : [
"[100.0, 100.0]"
]
}
},
{
"stage" : "IXSCAN",
"keyPattern" : {
"a" : 1
},
"indexName" : "a_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"a" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"a" : [
"[100.0, 100.0]"
]
}
}
]
}
}
]
关于database - 如何检查索引交集是否支持 MongoDB 中的查询?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56164822/