MongoDB 索引笔记

索引 (Index)

合适的索引可以大大提高数据库搜索性能

集合层面的索引

支持复合键索引可以对多个字段进行排序

复合索引:(A, B, C)

可以支持的索引:{A}, {A, B}, {A, B, C}
不可以支持的索引:{B}, {C}, {B, C}

索引的操作

  • db.collection.getIndexes()
  • db.collection.createIndex()
  • db.collection.dropIndex()
  • db.collection.dropIndexes()
  • cursor.explain()

索引的类型

  • 单键索引
  • 复合键索引
  • 多键索引 (针对数组字段)

索引的特性

  • 唯一性
  • 稀疏性
  • 生存时间

查询分析

  • 检视索引的效果 explain()

索引的选择
如何创建一个合适的索引
索引对数据库写入操作的影响

创建索引 db.collection.createIndex()

语法: db.collection.createIndex(keys, options, commitQuorum)

  • keys 文档指定创建索引的字段
> db.accuntsWithIndex.find()
{ "_id" : ObjectId("635f20261b8b77383133f018"), "name" : "alice", "balance" : 50, "currency" : [ "GBP", "USD" ] }
{ "_id" : ObjectId("635f20261b8b77383133f019"), "name" : "bob", "balance" : 20, "currency" : [ "AUD", "USD" ] }
{ "_id" : ObjectId("635f20261b8b77383133f01a"), "name" : "bob", "balance" : 300, "currency" : [ "CNY" ] }
>
// 创建索引
> db.accuntsWithIndex.createIndex({name: 1})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}
>
> db.accuntsWithIndex.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_"
	},
	{
		"v" : 2,
		"key" : {
			"name" : 1
		},
		"name" : "name_1"
	}
]

// 创建复合键索引
> db.accuntsWithIndex.createIndex({name: 1, balance: -1})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 2,
	"numIndexesAfter" : 3,
	"ok" : 1
}
> db.accuntsWithIndex.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_"
	},
	{
		"v" : 2,
		"key" : {
			"name" : 1
		},
		"name" : "name_1"
	},
	{
		"v" : 2,
		"key" : {
			"name" : 1,
			"balance" : -1
		},
		"name" : "name_1_balance_-1"
	}
]
>
// 创建一个多键索引 针对数组字段
> db.accuntsWithIndex.createIndex({currency: 1})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 3,
	"numIndexesAfter" : 4,
	"ok" : 1
}
> db.accuntsWithIndex.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_"
	},
	{
		"v" : 2,
		"key" : {
			"name" : 1
		},
		"name" : "name_1"
	},
	{
		"v" : 2,
		"key" : {
			"name" : 1,
			"balance" : -1
		},
		"name" : "name_1_balance_-1"
	},
	{
		"v" : 2,
		"key" : {
			"currency" : 1
		},
		"name" : "currency_1"
	}
]
  • options 文档定义了创建索引时可以使用的一些参数,也可以设定索引的特性

    • 索引的唯一性(_id 的索引就是唯一索引)
      • db.accuntsWithIndex.createIndex({balance: 1}, {unique: true})
      • 如果已有文档的某个字段有重复值,就不可以创建唯一索引,
      • 如果新增文档不包含唯一性索引,则只有第一篇缺失该字段的文档可以写入数据库,索引中该文档的键值被默认为null
      • 复合键的索引也可以具有唯一性,不同文档之间,所包含的复合键的组合,不可以重复
  • 索引的稀疏性 ``

    • 只将包含索引键字段的文档加入到索引中(即使索引键字段为null)
    • db.accuntsWithIndex.createIndex({balance: 1}, {sparse: true})
    • 如果同一个索引既具有唯一性,又具有稀疏性,就可以保存 多篇 缺失索引键的文档了
    • 复合键索引也可以具有稀疏性,只有在缺失复合键所有包含的字段的情况下,文档才不会被加入到索引中
  • 索引的生存时间

    • 针对日期字段,或者包含日期元素的数组字段,可以使用设定了生存时间的索引,来自动删除字段值超过生存时间的文档
    • db.accuntsWithIndex.createIndex({lastAccess: 1}, {expireAfterSeconds: 20}), lastAccess 与当前时间比较,超过20秒就被删除
    • 复合键索引 不具备 生存时间特性
    • 当索引时包含日期元素的数组字段时,数组中 最小 的日期将被用来计算文档是否已经过期
    • 数据库使用一个后台线程来监测和删除过期的文档,删除操作可能有一定的延迟
    > db.accuntsWithIndex.createIndex({balance: 1}, {unique: true})
    {
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
    }
      
    > db.accuntsWithIndex.createIndex({name: 1}, {unique: true})
    {
        "ok" : 0,
        "errmsg" : "Index build failed: de8b6096-0dd8-406b-a3c3-dc3911812710: Collection test.accuntsWithIndex ( dbe9339e-d1f8-4ed2-abe4-afce6ec06e7a ) :: caused by :: E11000 duplicate key error collection: test.accuntsWithIndex index: name_1 dup key: { name: \"bob\" }",
        "code" : 11000,
        "codeName" : "DuplicateKey",
        "keyPattern" : {
            "name" : 1
        },
        "keyValue" : {
            "name" : "bob"
        }
    }
      
    // 如果新增文档不包含唯一性索引,则只有**第一篇**缺失该字段的文档可以写入数据库,索引中该文档的键值被默认为null
    > db.accuntsWithIndex.find()
    { "_id" : ObjectId("635f20261b8b77383133f018"), "name" : "alice", "balance" : 50, "currency" : [ "GBP", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f019"), "name" : "bob", "balance" : 20, "currency" : [ "AUD", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f01a"), "name" : "bob", "balance" : 300, "currency" : [ "CNY" ] }
    >
    > db.accuntsWithIndex.insert({"name": "charlie", lastAccess: new Date()})
    WriteResult({ "nInserted" : 1 })
      
    > db.accuntsWithIndex.insert({"name": "david", lastAccess: new Date()})
    WriteResult({
        "nInserted" : 0,
        "writeError" : {
            "code" : 11000,
            "errmsg" : "E11000 duplicate key error collection: test.accuntsWithIndex index: balance_1 dup key: { balance: null }"
        }
    })
    > db.accuntsWithIndex.find()
    { "_id" : ObjectId("635f20261b8b77383133f018"), "name" : "alice", "balance" : 50, "currency" : [ "GBP", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f019"), "name" : "bob", "balance" : 20, "currency" : [ "AUD", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f01a"), "name" : "bob", "balance" : 300, "currency" : [ "CNY" ] }
    { "_id" : ObjectId("635ff408a5d828642b37ded7"), "name" : "charlie", "lastAccess" : ISODate("2022-10-31T16:12:56.554Z") }
    
    // 索引的稀疏性
    > db.accuntsWithIndex.createIndex({balance: 1}, {sparse: true})
    {
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
    }
    > db.accuntsWithIndex.getIndexes()
    [
        {
            "v" : 2,
            "key" : {
                "_id" : 1
            },
            "name" : "_id_"
        },
        {
            "v" : 2,
            "key" : {
                "balance" : 1
            },
            "name" : "balance_1",
            "sparse" : true
        }
    ]
    
    // 如果同一个索引既具有唯一性,又具有稀疏性,就可以保存 **多篇** 缺失索引键的文档了
    > db.accuntsWithIndex.createIndex({balance: 1}, {unique: true, sparse: true})
    {
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
    }
    > db.accuntsWithIndex.getIndexes()
    [
        {
            "v" : 2,
            "key" : {
                "_id" : 1
            },
            "name" : "_id_"
        },
        {
            "v" : 2,
            "unique" : true,
            "key" : {
                "balance" : 1
            },
            "name" : "balance_1",
            "sparse" : true
        }
    ]
    
    > db.accuntsWithIndex.find()
    { "_id" : ObjectId("635f20261b8b77383133f018"), "name" : "alice", "balance" : 50, "currency" : [ "GBP", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f019"), "name" : "bob", "balance" : 20, "currency" : [ "AUD", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f01a"), "name" : "bob", "balance" : 300, "currency" : [ "CNY" ] }
    { "_id" : ObjectId("635ff408a5d828642b37ded7"), "name" : "charlie", "lastAccess" : ISODate("2022-10-31T16:12:56.554Z") }
    >
    > db.accuntsWithIndex.insert({"name": "david", lastAccess: new Date()})
    WriteResult({ "nInserted" : 1 })
    > db.accuntsWithIndex.find()
    { "_id" : ObjectId("635f20261b8b77383133f018"), "name" : "alice", "balance" : 50, "currency" : [ "GBP", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f019"), "name" : "bob", "balance" : 20, "currency" : [ "AUD", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f01a"), "name" : "bob", "balance" : 300, "currency" : [ "CNY" ] }
    { "_id" : ObjectId("635ff408a5d828642b37ded7"), "name" : "charlie", "lastAccess" : ISODate("2022-10-31T16:12:56.554Z") }
    { "_id" : ObjectId("635ff7eda5d828642b37deda"), "name" : "david", "lastAccess" : ISODate("2022-10-31T16:29:33.427Z") }
    
    > db.accuntsWithIndex.find()
    { "_id" : ObjectId("635f20261b8b77383133f018"), "name" : "alice", "balance" : 50, "currency" : [ "GBP", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f019"), "name" : "bob", "balance" : 20, "currency" : [ "AUD", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f01a"), "name" : "bob", "balance" : 300, "currency" : [ "CNY" ] }
    { "_id" : ObjectId("635ff408a5d828642b37ded7"), "name" : "charlie", "lastAccess" : ISODate("2022-10-31T16:12:56.554Z") }
    { "_id" : ObjectId("635ff7eda5d828642b37deda"), "name" : "david", "lastAccess" : ISODate("2022-10-31T16:29:33.427Z") }
    >
    > db.accuntsWithIndex.createIndex({lastAccess: 1}, {expireAfterSeconds: 20})
    {
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
    }
    
    > db.accuntsWithIndex.find()
    { "_id" : ObjectId("635f20261b8b77383133f018"), "name" : "alice", "balance" : 50, "currency" : [ "GBP", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f019"), "name" : "bob", "balance" : 20, "currency" : [ "AUD", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f01a"), "name" : "bob", "balance" : 300, "currency" : [ "CNY" ] }
    
    // 插入新的文档
    > db.accuntsWithIndex.insert({"name": "david", lastAccess: new Date()})
    WriteResult({ "nInserted" : 1 })
    >
    > db.accuntsWithIndex.find()
    { "_id" : ObjectId("635f20261b8b77383133f018"), "name" : "alice", "balance" : 50, "currency" : [ "GBP", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f019"), "name" : "bob", "balance" : 20, "currency" : [ "AUD", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f01a"), "name" : "bob", "balance" : 300, "currency" : [ "CNY" ] }
    { "_id" : ObjectId("635ffb7ba5d828642b37dedb"), "name" : "david", "lastAccess" : ISODate("2022-10-31T16:44:43.770Z") }
    >
    // 等待20s 后文档被删除
    > db.accuntsWithIndex.find()
    { "_id" : ObjectId("635f20261b8b77383133f018"), "name" : "alice", "balance" : 50, "currency" : [ "GBP", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f019"), "name" : "bob", "balance" : 20, "currency" : [ "AUD", "USD" ] }
    { "_id" : ObjectId("635f20261b8b77383133f01a"), "name" : "bob", "balance" : 300, "currency" : [ "CNY" ] }
    

索引的效果 cursor.explain()

索引对查询操作的效果

> db.accuntsWithIndex.find({balance: 50}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "test.accuntsWithIndex",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"balance" : {
				"$eq" : 50
			}
		},
		"queryHash" : "88DDD986",
		"planCacheKey" : "9238DC63",
		"winningPlan" : {
			"stage" : "COLLSCAN",  // collection scan 查询效率低
			"filter" : {
				"balance" : {
					"$eq" : 50
				}
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "29ff2844a258",
		"port" : 27017,
		"version" : "4.4.16",
		"gitVersion" : "a7bceadbac919a2c035f2874c61d138fd75d6a6f"
	},
	"ok" : 1
}

> db.accuntsWithIndex.find({balance: 50}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "test.accuntsWithIndex",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"balance" : {
				"$eq" : 50
			}
		},
		"queryHash" : "88DDD986",
		"planCacheKey" : "9238DC63",
		"winningPlan" : {
			"stage" : "COLLSCAN",
			"filter" : {
				"balance" : {
					"$eq" : 50
				}
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "29ff2844a258",
		"port" : 27017,
		"version" : "4.4.16",
		"gitVersion" : "a7bceadbac919a2c035f2874c61d138fd75d6a6f"
	},
	"ok" : 1
}
>
> db.accuntsWithIndex.find({name: "bob"}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "test.accuntsWithIndex",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"name" : {
				"$eq" : "bob"
			}
		},
		"queryHash" : "01AEE5EC",
		"planCacheKey" : "0BE5F32C",
		"winningPlan" : { 
			"stage" : "FETCH",        // 通过 index,获取查询结果,如果结果中只包含索引值,则不会进行fetch操作
			"inputStage" : {
				"stage" : "IXSCAN",   // index scan 效率比较高
				"keyPattern" : {
					"name" : 1
				},
				"indexName" : "name_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"name" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"name" : [
						"[\"bob\", \"bob\"]"
					]
				}
			}
		},
		"rejectedPlans" : [
			{
				"stage" : "FETCH",
				"inputStage" : {
					"stage" : "IXSCAN",
					"keyPattern" : {
						"name" : 1,
						"balance" : -1
					},
					"indexName" : "name_1_balance_-1",
					"isMultiKey" : false,
					"multiKeyPaths" : {
						"name" : [ ],
						"balance" : [ ]
					},
					"isUnique" : false,
					"isSparse" : false,
					"isPartial" : false,
					"indexVersion" : 2,
					"direction" : "forward",
					"indexBounds" : {
						"name" : [
							"[\"bob\", \"bob\"]"
						],
						"balance" : [
							"[MaxKey, MinKey]"
						]
					}
				}
			}
		]
	},
	"serverInfo" : {
		"host" : "29ff2844a258",
		"port" : 27017,
		"version" : "4.4.16",
		"gitVersion" : "a7bceadbac919a2c035f2874c61d138fd75d6a6f"
	},
	"ok" : 1
}
>

// 通过 index,获取查询结果,如果结果中只包含索引值,则不会进行fetch操作
> db.accuntsWithIndex.find({name: "bob"}, {_id: 0, name: 1}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "test.accuntsWithIndex",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"name" : {
				"$eq" : "bob"
			}
		},
		"queryHash" : "3066FB64",
		"planCacheKey" : "A8F8C110",
		"winningPlan" : {
			"stage" : "PROJECTION_COVERED",
			"transformBy" : {
				"_id" : 0,
				"name" : 1
			},
			"inputStage" : {
				"stage" : "IXSCAN",
				"keyPattern" : {
					"name" : 1
				},
				"indexName" : "name_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"name" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"name" : [
						"[\"bob\", \"bob\"]"
					]
				}
			}
		},
		"rejectedPlans" : [
			{
				"stage" : "PROJECTION_COVERED",
				"transformBy" : {
					"_id" : 0,
					"name" : 1
				},
				"inputStage" : {
					"stage" : "IXSCAN",
					"keyPattern" : {
						"name" : 1,
						"balance" : -1
					},
					"indexName" : "name_1_balance_-1",
					"isMultiKey" : false,
					"multiKeyPaths" : {
						"name" : [ ],
						"balance" : [ ]
					},
					"isUnique" : false,
					"isSparse" : false,
					"isPartial" : false,
					"indexVersion" : 2,
					"direction" : "forward",
					"indexBounds" : {
						"name" : [
							"[\"bob\", \"bob\"]"
						],
						"balance" : [
							"[MaxKey, MinKey]"
						]
					}
				}
			}
		]
	},
	"serverInfo" : {
		"host" : "29ff2844a258",
		"port" : 27017,
		"version" : "4.4.16",
		"gitVersion" : "a7bceadbac919a2c035f2874c61d138fd75d6a6f"
	},
	"ok" : 1
}

索引对排序操作的效果

  • 排序与索引一致
  • 排序与索引不一致
// 排序与索引一致
>  db.accuntsWithIndex.find().sort({name: 1, balance: -1}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "test.accuntsWithIndex",
		"indexFilterSet" : false,
		"parsedQuery" : {

		},
		"queryHash" : "DC9EFEDE",
		"planCacheKey" : "DC9EFEDE",
		"winningPlan" : {
			"stage" : "FETCH",
			"inputStage" : {
				"stage" : "IXSCAN",  // 使用索引排序,然后fetch 文档
				"keyPattern" : {
					"name" : 1,
					"balance" : -1
				},
				"indexName" : "name_1_balance_-1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"name" : [ ],
					"balance" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"name" : [
						"[MinKey, MaxKey]"
					],
					"balance" : [
						"[MaxKey, MinKey]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "29ff2844a258",
		"port" : 27017,
		"version" : "4.4.16",
		"gitVersion" : "a7bceadbac919a2c035f2874c61d138fd75d6a6f"
	},
	"ok" : 1
}

// 排序与索引不一致
> db.accuntsWithIndex.find().sort({name: 1, balance: 1}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "test.accuntsWithIndex",
		"indexFilterSet" : false,
		"parsedQuery" : {

		},
		"queryHash" : "797A24CD",
		"planCacheKey" : "797A24CD",
		"winningPlan" : {
			"stage" : "SORT",    // 先加载索引文档到内容,让排序,耗费内存,效率不高
			"sortPattern" : {
				"name" : 1,
				"balance" : 1
			},
			"memLimit" : 104857600,
			"type" : "simple",
			"inputStage" : {
				"stage" : "COLLSCAN",
				"direction" : "forward"
			}
		},
		"rejectedPlans" : [ ]
	},
	"serverInfo" : {
		"host" : "29ff2844a258",
		"port" : 27017,
		"version" : "4.4.16",
		"gitVersion" : "a7bceadbac919a2c035f2874c61d138fd75d6a6f"
	},
	"ok" : 1
}
>

删除索引 db.collection.dropIndex()

  • 如果需要更改某些字段上已经创建的索引,必须先删除原索引,再重新创建新索引
  • 使用索引名称删除索引
  • 使用索引定义删除索引
// 列出集合中索引
> db.accuntsWithIndex.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_"
	},
	{
		"v" : 2,
		"key" : {
			"name" : 1
		},
		"name" : "name_1"
	},
	{
		"v" : 2,
		"key" : {
			"name" : 1,
			"balance" : -1
		},
		"name" : "name_1_balance_-1"
	},
	{
		"v" : 2,
		"key" : {
			"currency" : 1
		},
		"name" : "currency_1"
	}
]

// 删除 'name_1' 索引
> db.accuntsWithIndex.dropIndex("name_1")
{ "nIndexesWas" : 4, "ok" : 1 }
>
> db.accuntsWithIndex.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_"
	},
	{
		"v" : 2,
		"key" : {
			"name" : 1,
			"balance" : -1
		},
		"name" : "name_1_balance_-1"
	},
	{
		"v" : 2,
		"key" : {
			"currency" : 1
		},
		"name" : "currency_1"
	}
]

// 使用索引定义删除索引
> db.accuntsWithIndex.dropIndex({"name": 1, "balance": -1})
{ "nIndexesWas" : 3, "ok" : 1 }
> db.accuntsWithIndex.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_"
	},
	{
		"v" : 2,
		"key" : {
			"currency" : 1
		},
		"name" : "currency_1"
	}
]
>

posted @ 2022-11-01 00:51  郭赫伟  阅读(35)  评论(0编辑  收藏  举报