在MongoDB中创建多键索引

发布于

在MongoDB中,当您在包含数组的字段上创建索引时,它会自动创建为多键索引。

多键索引支持对数组字段进行高效查询。

可以为保存标量数据(例如字符串、数字等)和嵌套文档的数组创建多键索引。

例

假设我们有一个名为 products 的集合,其中包含以下文档:

{ "_id" : 1, "product" : "Bat", "sizes" : [ "S", "M", "L" ] }
{ "_id" : 2, "product" : "Hat", "sizes" : [ "S", "L", "XL" ] }
{ "_id" : 3, "product" : "Cap", "sizes" : [ "M", "L" ] }

我们可以在该集合上创建一个多键索引,如下所示:

db.products.createIndex(
   {
     "sizes": 1
   }
)

这就像创建常规索引一样。无需显式指定它是多键索引。MongoDB能够确定该字段是否包含一个数组,因此将其创建为多键索引。

使用多键索引,MongoDB为数组中的每个元素创建一个索引键。

嵌入式文档上的复合多键索引

如前所述,您可以为保存嵌入文档的数组创建多键索引。

您可以对这些字段创建复合索引,以便针对数组中的多个字段创建索引。

假设我们有一个集合,其中包含如下文档:restaurants

db.restaurants.insertMany([ { _id: 1, name: “The Rat”, reviews: [{ name: “Stanley”, date: “04 December, 2020”, ordered: “Dinner”, rating: 1 }, { name: “Tom”, date: “04 October, 2020”, ordered: “Lunch”, rating: 2 }] }, { _id: 2, name: “Yum Palace”, reviews: [{ name: “Stacey”, date: “08 December, 2020”, ordered: “Lunch”, rating: 3 }, { name: “Tom”, date: “08 October, 2020”, ordered: “Breakfast”, rating: 4 }] }, { _id: 3, name: “Boardwalk Cafe”, reviews: [{ name: “Steve”, date: “20 December, 2020”, ordered: “Breakfast”, rating: 5 }, { name: “Lisa”, date: “25 October, 2020”, ordered: “Dinner”, rating: 5 }, { name: “Kim”, date: “21 October, 2020”, ordered: “Dinner”, rating: 5 }] } ])

我们可以创建一个复合多键索引,如下所示:

db.restaurants.createIndex( 
  { 
    "reviews.ordered": 1,
    "reviews.rating": -1
  } 
)

现在,每当我们运行涉及这些字段的查询时,都会使用多键索引。

以下是我们搜索其中一个字段时查询计划的外观:

db.restaurants.find( { "reviews.ordered": "Dinner" } ).explain()

输出:

{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "krankykranes.restaurants",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"reviews.ordered" : {
				"$eq" : "Dinner"
			}
		},
		"queryHash" : "A01226B4",
		"planCacheKey" : "0E761583",
		"winningPlan" : {
			"stage" : "FETCH",
			"inputStage" : {
				"stage" : "IXSCAN",
				"keyPattern" : {
					"reviews.ordered" : 1,
					"reviews.rating" : -1
				},
				"indexName" : "reviews.ordered_1_reviews.rating_-1",
				"isMultiKey" : true,
				"multiKeyPaths" : {
					"reviews.ordered" : [
						"reviews"
					],
					"reviews.rating" : [
						"reviews"
					]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"reviews.ordered" : [
						"[\"Dinner\", \"Dinner\"]"
					],
					"reviews.rating" : [
						"[MaxKey, MinKey]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"ok" : 1
}

读取的部分表示它进行了索引扫描。如果它没有使用索引,它将执行集合扫描 ()。IXSCAN``COLLSCAN

当我们执行涉及索引中两个字段的查询时,情况相同:

db.restaurants.find( { "reviews.ordered": "Dinner", "reviews.rating": { $gt: 3 } } ).explain()

输出:

{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "krankykranes.restaurants",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"$and" : [
				{
					"reviews.ordered" : {
						"$eq" : "Dinner"
					}
				},
				{
					"reviews.rating" : {
						"$gt" : 3
					}
				}
			]
		},
		"queryHash" : "C770E210",
		"planCacheKey" : "447B5666",
		"winningPlan" : {
			"stage" : "FETCH",
			"filter" : {
				"reviews.rating" : {
					"$gt" : 3
				}
			},
			"inputStage" : {
				"stage" : "IXSCAN",
				"keyPattern" : {
					"reviews.ordered" : 1,
					"reviews.rating" : -1
				},
				"indexName" : "reviews.ordered_1_reviews.rating_-1",
				"isMultiKey" : true,
				"multiKeyPaths" : {
					"reviews.ordered" : [
						"reviews"
					],
					"reviews.rating" : [
						"reviews"
					]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"reviews.ordered" : [
						"[\"Dinner\", \"Dinner\"]"
					],
					"reviews.rating" : [
						"[MaxKey, MinKey]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"ok" : 1
}

但是,如果查询中的某个字段未包含在索引中,则会导致集合扫描:

db.restaurants.find( { "reviews.name": "Lisa", "reviews.rating": { $gt: 3 } } ).explain()

输出:

{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "krankykranes.restaurants",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"$and" : [
				{
					"reviews.name" : {
						"$eq" : "Lisa"
					}
				},
				{
					"reviews.rating" : {
						"$gt" : 3
					}
				}
			]
		},
		"queryHash" : "49EF83EC",
		"planCacheKey" : "3C60321C",
		"winningPlan" : {
			"stage" : "COLLSCAN",
			"filter" : {
				"$and" : [
					{
						"reviews.name" : {
							"$eq" : "Lisa"
						}
					},
					{
						"reviews.rating" : {
							"$gt" : 3
						}
					}
				]
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"ok" : 1
}