文章目录
MongoDB索引
索引支持在MongoDB中高效执行查询。如果没有索引,MongoDB通过执行Collection scan ,即扫描集合中的每个文档,匹配查询语句的文档。如果查询存在适当的索引,MongoDB可以使用该索引来减少扫描的文档数量,MongoDB底层的索引结构是一颗B-树。
在创建集合时,MongoDB默认在_id
字段上创建唯一索引。_id
索引防止插入两个相同的_id
字段文档。也不能删除_id
字段上的索引。
创建索引语法
// 语法
> db.collection.createIndex( <key and index type specification>, <options> )
// collection集合上的name字段创建降序索引
> db.collection.createIndex( { name: -1 } )
索引类型
单值索引
除了MongoDB默认创建的_id
索引之外,MongoDB还支持在文档的单个字段上创建升序/降序索引。
对于单字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以按任意方向遍历索引。
假设有如下文档
db.records.insert({"score":1000,"location":{state:"NY",city:"New York"}})
示例:
// 需要在score上创建升序索引
// 1表示升序,-1表示降序
db.records.createIndex( { score: 1 } )
// score索引支持下面2个查询
db.records.find( { score: 2 } )
db.records.find( { score: { $gt: 10 } } )
// 创建嵌套字段索引
db.records.createIndex( { "location.state": 1 } )
// location.state索引支持下面查询
db.records.find( { "location.state": "CA" } )
db.records.find( { "location.city": "Albany", "location.state": "NY" } )
//在嵌入文档创建索引
//location字段是一个嵌入的文档,包含嵌入的字段city和state。下面的命令在location字段上创建一个完整的索引
db.records.createIndex( { location: 1 } )
// 支持的查询
db.records.find( { location: { city: "New York", state: "NY" } } )
组合索引(Compound Index)
MongoDB还支持在多个字段创建索引,组合索引中字段的顺序对性能有很大影响,一般会遵循ESR原则,即:
- Equal 等值查询的字段放最前面
- Sort 条件放中间,也就是sort字段放中间
- Range 匹配的字段放后面,这里的匹配指的是=,>,<等这类匹配
ESR原则示例:
假设有这样一个查询
// 索引为 a,c,b
// 则可以通过a定位,c直接就是有序的了,接下来对b内存中匹配
db.test.find({a: 2,b: {$gte:2,$lte:3}.sort({c: 1})})
创建组合索引语法
db.collection.createIndex( { <field1>: <type>, <field2>: <type2>, ... } )
假设下列文档
db.products.insert({
"item": "Banana",
"category": ["food", "produce", "grocery"],
"location": "4th Street Store",
"stock": 4,
"type": "cases"
})
示例:
// 创建item,stock组合索引
db.products.createIndex( { "item": 1, "stock": 1 } )
// 支持类似下列查询
db.products.find( { item: "Banana" } )
db.products.find( { item: "Banana", stock: { $gt: 5 } } )
注意点:
- 对于组合索引,字段排序顺序(升序/降序)在索引排序操作时很重要。
- 左前缀索引特性,查询必须包含前面的字段,才能用到索引。
多键索引(Multikey Index)
MongoDB使用多键索引来索引数组中存储的内容。如果索引一个包含数组值的字段,MongoDB会为数组的每个元素创建单独的索引项。这些多键索引允许查询通过匹配一个或多个数组元素来选择包含数组的文档。MongoDB自动决定是否创建一个多键索引,如果索引字段包含一个数组值;您不需要显式地指定多值索引。
创建语法:
// 如果索引字段是一个数组,MongoDB会自动创建一个多键索引;您不需要显式地指定多键索引。
db.coll.createIndex( { <field>: < 1 or -1 > } )
假设有下面文档
db.survey.insert({
item: "ABC", ratings: [ 2, 5, 9 ]
})
示例:
// 创建ratings上的索引就会自动生成多键索引
db.survey.createIndex( { ratings: 1 } )
假设有个嵌套的文档
db.inventory.insert({
item: "abc",
stock: [
{ size: "S", color: "red", quantity: 25 },
{ size: "S", color: "blue", quantity: 10 },
{ size: "M", color: "blue", quantity: 50 }
]
})
db.inventory.insert({
item: "def",
stock: [
{ size: "S", color: "blue", quantity: 20 },
{ size: "M", color: "blue", quantity: 5 },
{ size: "M", color: "black", quantity: 10 },
{ size: "L", color: "red", quantity: 2 }
]
})
db.inventory.insert({
item: "ijk",
stock: [
{ size: "M", color: "blue", quantity: 15 },
{ size: "L", color: "blue", quantity: 100 },
{ size: "L", color: "red", quantity: 25 }
]
})
示例:
// 创建索引
db.inventory.createIndex( { "stock.size": 1, "stock.quantity": 1 } )
// 对下列查询有限
db.inventory.find( { "stock.size": "M" } )
db.inventory.find( { "stock.size": "S", "stock.quantity": { $gt: 20 } } )
db.inventory.find( ).sort( { "stock.size": 1, "stock.quantity": 1 } )
db.inventory.find( { "stock.size": "M" } ).sort( { "stock.quantity": 1 } )
限制:
- 不能创建2个数组的组合索引
// 不能创建索引{a:1,b:1}
{ _id: 1, a: [ 1, 2 ], b: [ 1, 2 ], category: "AB - both arrays" }
// 下列情况允许使用复合多键索引{a: 1, b: 1},在创建复合多键索引之后,如果您试图插入一个a和b字段都是数组的文档,将无法插入。
{ _id: 1, a: [1, 2], b: 1, category: "A array" }
{ _id: 2, a: 1, b: [1, 2], category: "B array" }
//也可以创建一个组合索引{ "a.x": 1, "a.z": 1 }
{ _id: 1, a: [ { x: 5, z: [ 1, 2 ] }, { z: [ 1, 2 ] } ] }
{ _id: 2, a: [ { x: 5 }, { z: 4 } ] }
- 散列索引不能是多键的
地理位置索引(Geospatial Index)
为了支持对地理空间坐标数据的高效查询,MongoDB提供了两个特殊的索引:在返回结果时使用平面几何的2d索引和使用球面几何返回结果的2dsphere索引。
// 创建地理位置索引
db.geo_col.createIndex(
{location: "2d"},
{min:-20,max:20,bits:10}
{collation: {locale: "simple"}}
)
// 查询,给定box长方形,查看1,1到3,3范围的所有点
db.geo_col.find(
{ location:
{$geoWithin:
{$box: [[1,1],[3,3]]}
}
}
)
全文索引(Text Indexes)
MongoDB提供全文索引来支持对字符串内容的搜索查询。全文索引可以包含值为字符串或字符串元素数组的任何字段。
创建方式:
// 单字段comments上创建全文索引
db.reviews.createIndex( { comments: "text" } )
// 组合字段全文索引
db.reviews.createIndex(
{
subject: "text",
comments: "text"
}
)
通配符索引
MongoDB支持在一个或一组字段上创建索引来支持查询。MongoDB支持动态模式.
例如有下面一个集合
db.userData.insert({ "userMetadata" : { "likes" : [ "dogs", "cats" ] } })
db.userData.insert({ "userMetadata" : { "dislikes" : "pickles" } })
db.userData.insert({ "userMetadata" : { "age" : 45 } })
db.userData.insert({ "userMetadata" : "inactive" })
可以通过通配符创建userMetadata, userMetadata.likes, userMetadata.dislikes, and userMetadata.age的单个索引
// 创建通配符索引
db.userData.createIndex( { "userMetadata.$**" : 1 } )
// 支持的查询
db.userData.find({ "userMetadata.likes" : "dogs" })
db.userData.find({ "userMetadata.dislikes" : "pickles" })
db.userData.find({ "userMetadata.age" : { $gt : 30 } })
db.userData.find({ "userMetadata" : "inactive" })
哈希索引(Hashed Indexes)
为了支持基于哈希的分片,MongoDB提供了一个哈希索引类型,用于索引字段值的hash。这些索引的值在其范围内的分布更加随机,但是只支持相等匹配,不支持基于范围的查询。
创建哈希索引
db.collection.createIndex( { _id: "hashed" } )
索引的特性
- 唯一索引:索引的惟一属性使MongoDB索引字段的唯一性。除了unique约束之外,unique索引在功能上可以与其他索引类似。
// 唯一索引创建方式
db.members.createIndex( { "user_id": 1 }, { unique: true } )
- 部分索引(Partial Indexes):只对满足指定条件的collection中的文档进行部分索引。通过为集合中的一个文档子集建立索引,部分索引具有更低的存储需求,并降低了索引创建和维护的性能成本。
部分索引提供了sparse索引功能的超集,应该优先使用部分索引。
// 创建组合索引,该索引仅对rating字段大于5的文档进行索引
db.restaurants.createIndex(
{ cuisine: 1, name: 1 },
{ partialFilterExpression: { rating: { $gt: 5 } } }
)
- Sparse Indexes:索引的稀疏属性确保索引只包含具有索引字段的文档的条目。索引跳过没有索引字段的文档。
可以将Sparse Indexes选项与惟一索引选项组合使用,以防止插入具有重复索引字段值的文档,并跳过缺少索引字段的索引文档。
// 创建稀疏索引
db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )
- TTL Indexes:TTL索引是特殊的索引,MongoDB可以使用它在一段时间后自动从集合中删除文档。这对于某些类型的信息非常理想,比如机器生成的事件数据、日志和会话信息,这些信息只需要在数据库中保存有限的时间。
// 要创建TTL索引,使用db.collection.createIndex()方法和expireAfterSeconds选项创建一个字段,该字段的值是一个日期或一个包含日期值的数组。
db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 } )
// TTL索引在索引字段值之后经过指定的秒数后过期文档;也就是说,过期阈值是索引字段值加上指定的秒数。
索引管理
- 查看集合上存在的索引
db.col.getIndexes()
- 查看所有数据库上存在的索引
db.getCollectionNames().forEach(function(collection) {
indexes = db[collection].getIndexes();
print("Indexes for " + collection + ":");
printjson(indexes);
});
- 移除指定索引
db.accounts.dropIndex( { "tax-id": 1 } )
- 移除集合上所有索引
db.accounts.dropIndexes()
- 查看索引状态
> db.products.aggregate( [ { $indexStats: { } } ]).pretty()
{
"name" : "_id_",
"key" : {
"_id" : 1
},
"host" : "localhost.localdomain:27017",
"accesses" : {
"ops" : NumberLong(0),
"since" : ISODate("2020-01-17T05:13:49.967Z")
}
}
{
"name" : "item_1_stock_1",
"key" : {
"item" : 1,
"stock" : 1
},
"host" : "localhost.localdomain:27017",
"accesses" : {
"ops" : NumberLong(2),
"since" : ISODate("2020-01-17T05:15:43.337Z")
}
}
- 使用hint强制使用某个索引
db.people.find(
{ name: "John Doe", zipcode: { $gt: "63000" } }
).hint( { zipcode: 1 } )
查看执行计划
查看语法:
// 查看执行计划
db.col.find({name:200}).explain(true)
"winningPlan" : {
"stage" : <STAGE1>,
...
"inputStage" : {
"stage" : <STAGE2>,
...
"inputStage" : {
"stage" : <STAGE3>,
...
}
}
},
每个stage将其结果(即文档或索引键)传递给父节点。叶子节点通过文档扫描或者索引扫描,汇总结果倒根节点,stage是获取结果集的最后一个阶段,有以下几种可能:
- COLLSCAN: 集合扫描
- IXSCAN: 索引扫描
- FETCH: 覆盖索引
- SHARD_MERGE: 从各个shard合并结果
- SHARDING_FILTER: 从分片中过滤结果
测试有无索引区别
// 插入测试数据
> for (var i = 1 ; i<10000 ; i++) db.col.insert({name:i,age:i,date:new Date()})
// 查询
> db.col.find({name:200}).explain(true)
{
"queryPlanner" : { //关于查询优化器选择查询计划的信息。
"plannerVersion" : 1,
"namespace" : "test.col", //命名空间,test库的col集合
"indexFilterSet" : false,
"parsedQuery" : {// 查询的形状
"name" : {// name字段 等于200
"$eq" : 200
}
},
"winningPlan" : {// 说明查询优化器选择的执行计划的信息,将执行计划分为阶段树,例如一个阶段可以有一个inputStage,如果该阶段有多个子阶段则可以有inputStages
"stage" : "COLLSCAN",//集合扫描
"filter" : {//过滤的字段
"name" : {
"$eq" : 200
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"executionStats" : {//描述获胜计划的完整查询执行的统计信息。对于写操作,已完成的查询执行指的是将要执行的修改,但不将修改应用于数据库。
"executionSuccess" : true,
"nReturned" : 1,//匹配查询条件的文档数量
"executionTimeMillis" : 5,//查询计划选择和查询执行所需的总时间(毫秒)。
"totalKeysExamined" : 0,//扫描的索引项数
"totalDocsExamined" : 9999,//在查询执行期间检查的文档数量。
"executionStages" : {// 执行的阶段
"stage" : "COLLSCAN",//集合扫描
"filter" : {//过滤的字段
"name" : {
"$eq" : 200
}
},
"nReturned" : 1,//匹配查询条件的文档数
"executionTimeMillisEstimate" : 0,
"works" : 10001,
"advanced" : 1,
"needTime" : 9999,
"needYield" : 0,
"saveState" : 78,
"restoreState" : 78,
"isEOF" : 1,
"direction" : "forward",
"docsExamined" : 9999
},
"allPlansExecution" : [ ]
},
"serverInfo" : {//mongodb服务信息
"host" : "localhost.localdomain",
"port" : 28018,
"version" : "4.2.1",
"gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
},
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1581855244, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1581855244, 1)
}
创建索引对比
//创建索引
> db.col.createIndex({name:1})
> db.col.find({name:200}).explain(true)
{
"queryPlanner" : {//关于查询优化器选择查询计划的信息。
"plannerVersion" : 1,
"namespace" : "test.col",//命名空间,test库的col集合
"indexFilterSet" : false,
"parsedQuery" : {// 查询的形状
"name" : {// name字段 等于200
"$eq" : 200
}
},
"winningPlan" : {// 说明查询优化器选择的执行计划的信息,将执行计划分为阶段树,例如一个阶段可以有一个inputStage,如果该阶段有多个子阶段则可以有inputStages
"stage" : "FETCH",
"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" : [
"[200.0, 200.0]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {//描述获胜计划的完整查询执行的统计信息。对于写操作,已完成的查询执行指的是将要执行的修改,但不将修改应用于数据库。
"executionSuccess" : true,
"nReturned" : 1,//匹配查询条件的文档数量
"executionTimeMillis" : 0,//查询计划选择和查询执行所需的总时间(毫秒)。
"totalKeysExamined" : 1,//扫描的索引项数
"totalDocsExamined" : 1,//在查询执行期间检查的文档数量。
"executionStages" : {
"stage" : "FETCH",// 覆盖索引
"nReturned" : 1,
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"docsExamined" : 1,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",//索引扫描
"nReturned" : 1,//返回的行数
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"keyPattern" : {
"name" : 1
},
"indexName" : "name_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[200.0, 200.0]"
]
},
"keysExamined" : 1,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0
}
},
"allPlansExecution" : [ ]
},
"serverInfo" : {//mongodb服务信息
"host" : "localhost.localdomain",
"port" : 28018,
"version" : "4.2.1",
"gitVersion" : "edf6d45851c0b9ee15548f0f847df141764a317e"
},
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1581855566, 2),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1581855566, 2)
}
参考文档
- explain: https://docs.mongodb.com/manual/reference/explain-results/index.html
- 索引:https://docs.mongodb.com/manual/reference/explain-results/index.html