为什么要使用索引?
当我们在使用mongodb查询操作的时候,每次都需要遍历整个collection去查找匹配条件的数据,对于少量数据来说可能没有性能方面的影响, 但是如果查询的collection中有百万甚至千万的数据,那遍历整个collection对性能影响就很大了,可能一次查询需要花十多秒。所以, 我们就需要使用索引来优化查询。
MongoDB索引类型
MongoDB支持多种类型的索引,包括单字段索引、复合索引、多key索引、地理位置索引、文本索引等,每种类型的索引有不同的使用场合。下面我们依次介绍
我们首先进入到mongo shell创建一个测试数据库testIndex
> use testIndex
创建一个User表并插入几条测试数据
> db.User.insertMany([
... { name: "张三", email: "zhangsan@qq.com", phone: "13222312222", age: 21, address: ["张三公司地址", "张三家庭地址"] },
... { name: "李四", email: "lisi@qq.com", phone: "13322218909", age: 20, address: ["李四公司地址", "李四家庭地址"] },
... { name: "赵五", email: "zhaowu@qq.com", phone: "13733216673", age: 25, address: ["赵五公司地址", "赵五家庭地址"] }
... ])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("5d80f58acb1cb147170b97a6"),
ObjectId("5d80f58acb1cb147170b97a7"),
ObjectId("5d80f58acb1cb147170b97a8")
]
}
在没有创建index的情况下我们使用explain来分析一下普通查询操作
> db.User.explain().find({name: "张三"})
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.User",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "张三"
}
},
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"name" : {
"$eq" : "张三"
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "watsons-MacBook-Pro.local",
"port" : 27017,
"version" : "4.0.3",
"gitVersion" : "7ea530946fa7880364d88c8d8b6026bbc9ffa48c"
},
"ok" : 1
}
可以看到winningPlan.stage = "COLLSCAN", 意思是collection scan, 即全表扫描,这样的查询需要扫描整个集合进行筛选,性能消耗很大.
单字段索引
为name字段创建索引
> db.User.createIndex({name: 1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
可以看到返回的结果中numIndexesAfter:2,明明我们只为name一个字段创建了索引, 为什么会出现2个索引呢?原因是MongoDB默认为每条数据的_id字段加了索引,我们可以使用以下命定来查看所有索引
> db.User.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.User"
},
{
"v" : 2,
"key" : {
"name" : 1
},
"name" : "name_1",
"ns" : "test.User"
}
]
可以看到第一个索引的key是_id。
接下来我们使用name来作为查询条件进行查找并使用explain方法来分析此次查找
> db.User.explain().find({name: "张三"})
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.User",
"indexFilterSet" : false,
"parsedQuery" : {
"name" : {
"$eq" : "张三"
}
},
"winningPlan" : {
"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" : [
"[\"张三\", \"张三\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "watsons-MacBook-Pro.local",
"port" : 27017,
"version" : "4.0.3",
"gitVersion" : "7ea530946fa7880364d88c8d8b6026bbc9ffa48c"
},
"ok" : 1
}
注意看winningPlan. inputStage.stage = "IXSCAN", "IXSCAN"的意思是index scan, 即根据index扫描。到这里我们可以很确定此次查询使用到了索引.
复合索引
下面来看看复合索引的创建,
db.User.createIndex({name: 1, age: -1})
我们为name和age字段同时创建一个索引,name为升序, age为降序
下面我们来测试这个复合索引
> db.User.explain().find({name: "张三", age: 21})
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.User",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [
{
"age" : {
"$eq" : 21
}
},
{
"name" : {
"$eq" : "张三"
}
}
]
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"name" : 1,
"age" : -1
},
"indexName" : "name_1_age_-1",
"isMultiKey" : false,
"multiKeyPaths" : {
"name" : [ ],
"age" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"name" : [
"[\"张三\", \"张三\"]"
],
"age" : [
"[21.0, 21.0]"
]
}
}
},
"rejectedPlans" : [
{
"stage" : "FETCH",
"filter" : {
"age" : {
"$eq" : 21
}
},
"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" : [
"[\"张三\", \"张三\"]"
]
}
}
}
]
},
"serverInfo" : {
"host" : "watsons-MacBook-Pro.local",
"port" : 27017,
"version" : "4.0.3",
"gitVersion" : "7ea530946fa7880364d88c8d8b6026bbc9ffa48c"
},
"ok" : 1
}
可以看到winningPlan. inputStage.stage = "IXSCAN",复合索引使用成功
** 这里需要注意的是复合索引需要满足prefix原则 **
例如我们创建了一个复合索引
> db.User.createIndex({name: 1, phone: 1, age: -1})
这个索引包含了一下prefix
{name: 1}
{name: 1, phone: 1}
{name: 1, phone: 1, age: -1}
查询条件匹配以上这个三种情况的都可以有效的使用到这个复合索引
多key索引
多key索引是指为数组字段创建索引
例如我们可以为User.address字段创建一个多key索引
> db.User.createIndex({address: 1})
那么在保存address的时候address中的元素会按照升序进行排列
文本索引
例如可以为name字段创建文本索引
> db.User.createIndex({name: "text"})
文本索引固定value为"text",文本索引有利于在大量文本内快速定位到某个字符, 我们可以用来做文章搜索功能
地理位置索引
MongoDB地理位置索引有两种
- 2d
- 2dsphere
2d位置索引一般用于查找平面内的位置
> db.User.createIndex(<filed>: "2d")
2dsphere位置索引一般用来查询球面位置
> db.User.createIndex(<filed>: "2dsphere")