在 MongoDB 中创建通配符索引

发布于

在MongoDB中创建索引有几种方法,从MongoDB 4.2开始,我们可以创建通配符索引。

通配符索引可以被认为是一种过滤器,它自动匹配集合中的任何字段、子文档或数组,然后为这些匹配项编制索引。

如果您的文档包含不同层次结构中具有不同字段的非结构化数据,则这可能很有用。在这种情况下,无法预测索引应该是什么,因为您不知道每个文档中将包含哪些数据。

通配符索引可用于此类非结构化数据,因为它们为字段的所有标量值编制索引,自动递归到任何子文档或数组中,并为子文档/数组中的所有标量字段编制索引。

示例集合

通配符索引并非适用于所有集合。您只会对某些集合创建通配符索引,其中包含包含不同层次结构中不同字段的非结构化数据的文档。

下面是一个名为 pets 的集合示例,该集合非常适合创建通配符索引:

{
	"_id" : 1,
	"name" : "Wag",
	"details" : {
		"type" : "Dog",
		"weight" : 20,
		"awards" : {
			"Florida Dog Awards" : "Top Dog",
			"New York Marathon" : "Fastest Dog",
			"Sumo 2020" : "Biggest Dog"
		}
	}
}
{
	"_id" : 2,
	"name" : "Fetch",
	"details" : {
		"born" : ISODate("2020-06-22T14:00:00Z"),
		"color" : "Black"
	}
}
{
	"_id" : 3,
	"name" : "Scratch",
	"details" : {
		"eats" : [
			"Mouse Porridge",
			"Bird Soup",
			"Caviar"
		],
		"type" : "Cat",
		"born" : ISODate("2020-12-19T14:00:00Z")
	}
}

此集合中的 3 个文档中的每一个都有一个字段,但它们在该字段中包含不同的字段。这是不一致的。这通常会使创建索引变得困难,因为我们不知道每个文档中将包含哪些字段。在仔细分析可能的文档结构后,我们可能需要创建多个索引。details

幸运的是,我们可以创建一个通配符索引。

但首先,让我们看一下查询其中一个字段时查询计划的外观。想象一下,我们想找出哪只狗在纽约马拉松比赛中获得了“最快的狗”奖。我们可以执行以下操作:

db.pets.find( { "details.awards.New York Marathon" : "Fastest Dog" } )

如果我们想检查查询计划,我们可以附加到末尾:explain()

db.pets.find( { "details.awards.New York Marathon" : "Fastest Dog" } ).explain()

这将返回以下内容:

{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "PetHotel.pets",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"details.awards.New York Marathon" : {
				"$eq" : "Fastest Dog"
			}
		},
		"queryHash" : "EC0D5185",
		"planCacheKey" : "EC0D5185",
		"winningPlan" : {
			"stage" : "COLLSCAN",
			"filter" : {
				"details.awards.New York Marathon" : {
					"$eq" : "Fastest Dog"
				}
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"ok" : 1
}

这告诉我们它将进行集合扫描(COLLSCAN),这意味着它必须扫描每个文档以查找该字段。

创建通配符索引

下面是为上述集合创建通配符索引的示例。

db.pets.createIndex({ "details.$**": 1 });

输出:

{ “createdCollectionAutomatically” : false, “numIndexesBefore” : 1, “numIndexesAfter” : 2, “ok” : 1 }

就是这样。已创建通配符索引。

为了创建通配符索引,我们使用了要在其上创建索引的字段名称(在本例中为字段),然后我们用点 () 附加它,然后是重要的部分,即部分。details``.``$**

指定应从此字段及其所有子文档创建通配符索引。$**

前缀 with 会将通配符索引的范围限制为仅字段。$**``details``details

现在让我们重新检查上述查询的查询计划:

db.pets.find( { "details.awards.New York Marathon" : "Fastest Dog" } ).explain()

结果:

{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "PetHotel.pets",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"details.awards.New York Marathon" : {
				"$eq" : "Fastest Dog"
			}
		},
		"queryHash" : "EC0D5185",
		"planCacheKey" : "7DFA23ED",
		"winningPlan" : {
			"stage" : "FETCH",
			"inputStage" : {
				"stage" : "IXSCAN",
				"keyPattern" : {
					"$_path" : 1,
					"details.awards.New York Marathon" : 1
				},
				"indexName" : "details.$**_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"$_path" : [ ],
					"details.awards.New York Marathon" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"$_path" : [
						"[\"details.awards.New York Marathon\", \"details.awards.New York Marathon\"]"
					],
					"details.awards.New York Marathon" : [
						"[\"Fastest Dog\", \"Fastest Dog\"]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"ok" : 1
}

这次,集合扫描 (COLLSCAN) 已被我们新创建的通配符索引上的索引扫描 (IXSCAN) 所取代。

我们字段中的每个字段都已作为路径/值编制索引,并且层次结构中的每个字段的索引中都有一个条目。其中字段值是子文档(例如我们的。 字段),索引已下降到子文档中并重复该过程。details``awards

在所有字段路径上创建通配符索引

在前面的示例中,我们在单个字段路径上创建了一个通配符索引。只需使用 即可在所有字段路径上创建通配符索引,而无需在它前面加上字段前缀。$**

例如,我们可以这样做:

db.pets.createIndex({ "$**": 1 });

这将在所有字段路径上创建一个通配符索引。

实际上,这并不完全正确。默认情况下,不会在字段上创建通配符索引。要包含该字段,您需要将其包含在文档中。_id``_id``wildcardProjection

无法创建通配符索引?检查此设置。

必须至少创建通配符索引。mongod``featureCompatibilityVersion``4.2

您可以使用以下代码检查此设置:

db.adminCommand( 
    { 
        getParameter: 1, 
        featureCompatibilityVersion: 1 
    } 
)

您可以使用以下命令进行设置:setFeatureCompatibilityVersion

db.adminCommand( { setFeatureCompatibilityVersion: "4.4" } )

该命令需要在数据库中运行。setFeatureCompatibilityVersion``admin