MongoDB基础之索引详解
1 索引
1.1 简介
1.1.1 索引定义
索引
通常能够极大的提高查询的效率,如果没有索引,MongoDB
在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中
,索引是对数据库表中一列或多列的值进行排序的一种结构
由于索引存在于RAM
中,所以覆盖索引 从索引中获取数据比通过扫描文档读取数据要快得多
MongoDB
中索引大部分是排序的,排序索引意义:
- 在
MongoDB
中,排序操作,可以通过从索引中按照索引的顺序获取文档的方式,来保证结果的有序性。 - 如果
MongoDB
的查询计划器(planner)没法从索引中得到排序顺序,那么它就需要在内存中对结果排序,相比于不用索引的排序
操作,用索引会有更好的性能。 - 关键不用索引的排序操作,会在用了超过32MB内存时终止,也就是说MongoDB只能支持32MB的非索引排序
1.1.2 索引限制
- 额外开销
每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果很少对集合进行读取操作,建议不使用索引。 - 内存(
RAM
)使用
由于索引是存储在内存(RAM
)中,应该确保该索引的大小不超过内存的限制。
如果索引的大小大于内存的限制,MongoDB
会删除一些索引,这将导致性能下降。 - 索引键限制
从2.6版本开始,如果现有的索引字段的值超过索引键的限制,MongoDB
中不会创建索引。 - 插入文档超过索引键限制
如果文档的索引字段值超过了索引键的限制,MongoDB
不会将任何文档转换成索引的集合。与mongorestore和mongoimport工具类似。 - 最大范围
集合中索引不能超过64
个
索引名的长度不能超过128
个字符
一个复合索引最多可以有31
个字段 - 查询限制
索引不能被以下的查询使用:- 正则表达式及非操作符,如
$nin
,$not
, 等。 - 算术运算符,如
$mod
, 等。 $where
子句
- 正则表达式及非操作符,如
1.2 创建索引
1.2.1 基本语法
MongoDB
使用 createIndex()
方法来创建索引
注意
:在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex()
,之后的版本使用了 db.collection.createIndex()
方法,ensureIndex()
还能用,但只是 createIndex()
的别名。
createIndex()
方法基本语法格式如下所示:
db.collection.createIndex(keys, options)
语法中 Key 值为要创建的索引字段,1
为指定按升序创建索引,如果想按降序来创建索引指定为 -1
即可
createIndex()
接收可选参数,可选参数列表如下:
Parameter | Type | Description |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 |
dropDups | Boolean | 3.0+版本已废弃。在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false. |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
v | index version | 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
创建索引指定升序
db.col.createIndex({"title":1})
createIndex() 方法中也可以设置使用多个字段创建索引(关系型数据库中称作复合索引)
>db.col.createIndex({"title":1,"description":-1})
在后台创建索引
db.values.createIndex({open: 1, close: 1}, {background: true})
通过在创建索引时加 background:true 的选项,让创建工作在后台执行
1.2.2 创建其他种类索引
- 创建唯一索引
db.collection.ensureIndex({filed.subfield:1/-1}, {unique:true});
- 创建稀疏索引
稀疏索引的特点:如果针对field
做索引,针对不含field列的文档,将不建立索引.与之相对,普通索引,会把该文档的field列的值认为NULL,并建索引.
适宜于: 小部分文档含有某列时.
db.collection.ensureIndex({field:1/-1},{sparse:true});
db.tea.find();
{ "_id" : ObjectId("5275f99b87437c610023597b"), "email" : "a@163.com" }
{ "_id" : ObjectId("5275f99e87437c610023597c"), "email" : "b@163.com" }
{ "_id" : ObjectId("5275f9e887437c610023597e"), "email" : "c@163.com" }
{ "_id" : ObjectId("5275fa3887437c6100235980") }
如上内容,最后一行没有email列,如果分别加普通索引,和稀疏索引,对于最后一行的email分别当成 null 和 忽略最后一行来处理.
根据{email:null}
来查询,前者能查到,而稀疏索引查不到最后一行.
- 创建哈希索引
哈希索引速度比普通索引快,但是,不能对范围查询进行优化.
适宜于---随机性强的散列
db.collection.ensureIndex({file:’hashed’});
- 重建索引
一个表经过很多次修改后,导致表的文件产生空洞,索引文件也如此,可以通过索引的重建,减少索引文件碎片,并提高索引的效率.
类似mysql中的optimize table
db.collection.reIndex()
1.3 查看删除索引
查看集合索引
db.col.getIndexes()
查看全部集合索引
db.system.index.find();
查看集合索引大小
db.col.totalIndexSize()
删除集合所有索引
db.col.dropIndexes()
删除集合指定索引
db.col.dropIndex("索引名称")
1.4 查询分析
MongoDB
查询分析可以确保我们所建立的索引是否有效,是查询语句性能分析的重要工具。
MongoDB
查询分析常用函数有:explain()
和 hint()
创建索引
db.test.createIndex({'title':1});
查询分析
db.test.find({'title':1}).explain();
分析结果:
// 1
{
"explainVersion": "1",
"queryPlanner": {
"namespace": "mydb.test",
"indexFilterSet": false,
"parsedQuery": {
"title": {
"$eq": 1
}
},
"queryHash": "6E0D6672",
"planCacheKey": "1953B8BB",
"maxIndexedOrSolutionsReached": false,
"maxIndexedAndSolutionsReached": false,
"maxScansToExplodeReached": false,
"winningPlan": {
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"title": 1
},
"indexName": "title_1",
"isMultiKey": false,
"multiKeyPaths": {
"title": [ ]
},
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": {
"title": [
"[1.0, 1.0]"
]
}
}
},
"rejectedPlans": [ ]
},
"command": {
"find": "test",
"filter": {
"title": 1
},
"$db": "mydb"
},
"ok": 1
}
1.4.1 索引分析 explain
explain
操作提供了查询信息,使用索引及查询统计等。有利于我们对索引的优化。
接下来我们在 users
集合中创建 gender
和 user_name
的索引:
>db.users.createIndex({gender:1,user_name:1})
现在在查询语句中使用 explain :
>db.users.find({gender:"M"},{user_name:1,_id:0}).explain()
1.4.2 强制使用索引 hint
虽然MongoDB
查询优化器一般工作的很不错,但是也可以使用 hint
来强制 MongoDB
使用一个指定的索引。
这种方法某些情形下会提升性能。 一个有索引的 collection
并且执行一个多字段的查询
如下查询实例指定了使用 gender 和 user_name 索引字段来查询:
>db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1})
可以使用 explain()
函数来分析以上查询:
>db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain()
1.5 嵌套索引
示例文档如下:
{
"address": {
"city": "Los Angeles",
"state": "California",
"pin": "123"
},
"tags": [
"music",
"cricket",
"blogs"
],
"name": "Tom Benzamin"
}
1.5.1 索引数组字段
假设我们基于标签来检索用户,为此我们需要对集合中的数组 tags
建立索引。
在数组中创建索引,需要对数组中的每个字段依次建立索引。所以在我们为数组 tags 创建索引时,会为 music、cricket、blogs三个值建立单独的索引。
使用以下命令创建数组索引:
>db.users.createIndex({"tags":1})
创建索引后,为了验证我们使用使用了索引,可以使用 explain 命令:
>db.users.find({tags:"cricket"}).explain()
1.5.2 索引子文档字段
假设我们需要通过city、state、pincode字段来检索文档,由于这些字段是子文档的字段,所以我们需要对子文档建立索引。
为子文档的三个字段创建索引,命令如下:
>db.users.createIndex({"address.city":1,"address.state":1,"address.pincode":1})
1.6 全文索引
文本索引(text
),主要用于支持全文索引,一个集合只允许一个text
索引,但是一个text索引可以包括多个字段
示例文档:
{
"post_text": "enjoy the mongodb articles on test",
"tags": [
"mongodb",
"test"
]
}
创建索引:
db.posts.createIndex({post_text:"text"})
使用全文索引
现在我们已经对 post_text 建立了全文索引,我们可以搜索文章中的关键词 test:
>db.posts.find({$text:{$search:"test"}})
删除全文索引
删除已存在的全文索引,可以使用 find 命令查找索引名:
>db.posts.getIndexes()
通过以上命令获取索引名,本例的索引名为post_text_text,执行以下命令来删除索引:
db.posts.dropIndex("post_text_text")
1.7 空间索引
在地图上的一个点,是有经纬度坐标的,多个点就连成线,多条线可以组成各种形状的图形
在MongoDB
中,用文档记录地球球体(地理坐标)上的位置信息,可以将数据存储为GeoJSON
对象,如果用文档记录几何平面(投影坐标)上的位置信息,可以将数据存储为legacy coordinate pairs
传统坐标对。
1.7.1 GeoJSON
1.7.1.1 简介
GeoJSON
是一种基于JSON
格式的地理空间数据交换格式。它定义了几种类型的JSON
对象,通过这些JSON
对象或其组合来表示地理空间数据的特征、性质和空间范围等信息。
GeoJSON
默认使用的是地理坐标参考系统(WGS-84),单位是十进制的度。
一个GeoJSON
对象可以是SFSQL
规范中定义的七种几何类型(Point、MultiPoint、LineString、MultiLineString,Polygon,MultiPolygon,GeometryCollection
)
比较常见的类型是:Point:坐标点,LineString:线条,Polygon:多边形
MongoDB
字段保存GeoJSON
对象数据格式如下:
<field>: { type: <geojson type> , coordinates: <coordinates> }
说明:
field
:字段名type
:GeoJSON
类型coordinates
:坐标点数组,不同的GeoJSON
类型格式不一样。
常见类型格式:
- 坐标点(Point)格式:
{ type: "Point", coordinates: [ 经度, 纬度 ] }
例子:
{ type: "Point", coordinates: [ 40, 5 ] }
- 线 (LineString)格式:
{ type: "LineString", coordinates: [ 坐标点1, 坐标点2, ...] }
例子:
{ type: "LineString", coordinates: [ [ 40, 5 ], [ 41, 6 ] ] }
- 多边形 (Polygon)可以由一条或者多条线组成。
格式:
{ type: "Polygon",coordinates: [ 线段1,线段2, .... ]}
1.7.1.2 示例
- 一条线段组成的图形
{
type: "Polygon",
coordinates: [
// 线段坐标,注意第一个和最后一个坐标,是一样的
[ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ]
]
}
- 多条线段组成的图形
{
type : "Polygon",
coordinates : [
[ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ], // 线段1
[ [ 2 , 2 ] , [ 3 , 3 ] , [ 4 , 2 ] , [ 2 , 2 ] ] // 线段2
]
}
1.7.2 地理位置索引
MongoDB
支持两种地理位置索引,用于加快GeoJSON
类型数据的查询速度
2dsphere
这是一种球面几何位置的索引类型,就是计算两点间距离的时候,2dsphere
会考虑地球是圆的
创建2dsphere索引例子:
db.collection.createIndex( { location : "2dsphere" } )
为location字段创建索引2d
是一种平面几何类型,计算两点间距离,就当成平面计算。
创建2d
索引例子:
db.collection.createIndex( { location : "2d" },{min:n:max:m} )
为location字段创建索引,并指定范围,若不指定,默认在[-180,180]
之间的2d
索引
1.7.3 地理信息查询类型
1.7.3.1 查询类型
MongoDB
提供以下空间查询操作:
name | Description |
---|---|
$geoIntersects |
查询几何对象与指定的GeoJSON对象相交的文档,判断两个图形之间是否有交集。2dsphere 索引支持该操作。 |
$geoWithin |
查询几何对象在指定的GeoJSON对象边界内的文档。2dsphere和2d索引都支持该操作,替代$within |
$near |
返回几何对象在指定点附近的文档。2dsphere和2d索引都支持该操作。 |
$nearSphere |
返回球体上某点附近的地理空间对象文档。2dsphere和2d索引都支持该操作 |
几何操作符
名字 | 描述 | 格式 |
---|---|---|
$box |
在$geoWithin 操作中使用传统坐标对(legacy coordinate pairs)指定矩形,只有2d index支持。 |
{ <location field>: { $geoWithin: { $box: [ [ <bottom left coordinates> ], [ <upper right coordinates> ] ] } } } |
$center |
在$geoWithin 操作中使用传统坐标对(legacy coordinate pairs)指定圆形,只有2d index支持。 |
{ <location field>: { $geoWithin: { $center: [ [ <x>,<y> ] , <radius> ] } } } |
$centerSphere |
当使用球面几何的地理空间查询时,在$geoWithin 操作中使用传统坐标对或GeoJSON对象,2d和2dsphere都支持 |
{ <location field>: { $geoWithin: { $centerSphere: [ [<x>, <y> ], <radius> ] } } } |
$geometry |
用于在空间查询操作中使用GeoJSON对象指定输入的几何对象。即:设置作为对比的基准坐标,2d和2dsphere都支持。 | $geometry: { type: "<GeoJSON object type>", coordinates: [ <coordinates> ] } |
$maxDistance |
用于过滤$near 和$nearSphere 查询结果,指定最大距离。单位由坐标系决定(对于GeoJSON的点对象使用米为单位)。2d和2dsphere都支持。 |
db.places.find( { loc: { $near: [ -74 , 40 ], $maxDistance: 10 } } ) |
$minDistance |
用于过滤$near 和$nearSphere 操作的查询结果,限定结果文档中的几何对象到中心点的最小距离。2d和2dsphere索引都支持。 |
db.places.find( { location: { $nearSphere: { $geometry: { type : “Point”, coordinates : [ -73.9667, 40.78 ] }, $minDistance: 1000, $maxDistance: 5000 }} } ) |
$polygon |
为$geoWithin 查询指定一个使用传统坐标对的多边形。只有2d索引支持该操作。 |
db.places.find( { loc: { $geoWithin: { $polygon: [ [ 0 , 0 ], [ 3 , 6 ], [ 6 , 0 ] ] } } } ) |
$uniqueDocs |
地理空间查询不返回重复的结果。从2.6开始就被废弃了,$uniqueDocs操作符对结果没有影响。 |
1.7.3.2 查询语法
$geoIntersects
操作使用$geometry
指定GeoJSON
对象
{
<location field>: {
$geoIntersects: {
$geometry: {
type: "<GeoJSON object type>" ,
coordinates: [ <coordinates> ]
}
}
}
}
$geoWithin
操作也是使用$geometry
指定一个Polygon或MultiPolygon
类型的GeoJSON
对象作为输入:
{
<location field>: {
$geoWithin: {
$geometry: {
type: <"Polygon" or "MultiPolygon"> ,
coordinates: [ <coordinates> ]
}
}
}
}
$near
操作与$maxDistance
和$minDistance
操作符一起使用,返回以指定点为中心点,在限定距离范围内的文档。$near
查询返回的数据,按距离由近到远排序。$near
操作的输入可以是GeoJSON
格式的数据也可以是坐标对的数据,对空间索引的要求有区别:
- 对
GeoJSON
类型的点,需要使用2dsphere索引 - 对坐标对格式的点数据,需要使用2d索引
//GeoJSON 格式输入,单位为米
{
<location field>: {
$near: {
$geometry: {
type: "Point" ,
coordinates: [ <longitude> , <latitude> ]
},
$maxDistance: <distance in meters>,
$minDistance: <distance in meters>
}
}
}
//传统坐标对格式输入,单位为弧度
{
$near: [ <x>, <y> ],
$maxDistance: <distance in radians>
}
$nearSphere
操作是针对地理坐标进行计算的,返回指定球面上距离中心点在某段范围内的文档。当然,地理坐标可以存储为GeoJSON
的格式,也可以存储为传统坐标对的形式。
当文档中的几何数据格式是GeoJSON
时,建议使用GeoJSON
类型的点作为输入,且使用2dsphere
索引;
当文档中的位置信息格式是传统坐标对时,使用传统坐标对作为输入,且使用2d索引。其实$nearSphere
操作也可以在GeoJSON
格式的数据上使用2d索引。
//GeoJSON 格式输入,单位为米
{
$nearSphere: {
$geometry: {
type : "Point",
coordinates : [ <longitude>, <latitude> ]
},
$minDistance: <distance in meters>,
$maxDistance: <distance in meters>
}
}
//传统坐标对格式输入,单位为弧度
{
$nearSphere: [ <x>, <y> ],
$minDistance: <distance in radians>,
$maxDistance: <distance in radians>
}
1.7.3.3 查询案例
准备数据
var map = [{
"gis" : {
"x" : 185,
"y" : 150
}
},{
"gis" : {
"x" : 70,
"y" : 180
}
},{
"gis" : {
"x" : 75,
"y" : 180
}
},{
"gis" : {
"x" : 185,
"y" : 185
}
},{
"gis" : {
"x" : 65,
"y" : 185
}
},{
"gis" : {
"x" : 50,
"y" : 50
}
},{
"gis" : {
"x" : 50,
"y" : 50
}
},{
"gis" : {
"x" : 60,
"y" : 55
}
},{
"gis" : {
"x" : 65,
"y" : 80
}
},{
"gis" : {
"x" : 55,
"y" : 80
}
},{
"gis" : {
"x" : 0,
"y" : 0
}
},{
"gis" : {
"x" : 0,
"y" : 200
}
},{
"gis" : {
"x" : 200,
"y" : 0
}
},{
"gis" : {
"x" : 200,
"y" : 200
}
}]
for(var i = 0;i<map.length;i++){
db.map.insert(map[i])
}
创建索引并查询
创建索引并指定索引范围
db.map.createIndex({"gis":"2d"},{min:-1,max:201});
查询点(70,180)最近的3个点
db.map.find({"gis":{$near:[70,180]}},{gis:1,_id:0}).limit(3)
查询以点(50,50)和点(190,190)为对角线的正方形中的所有的点
db.map.find({gis:{"$geoWithin":{$box:[[50,50],[190,190]]}}},{_id:0,gis:1})
查询出以圆心为(56,80)半径为50规则下的圆心面积中的点
db.map.find({gis:{$geoWithin:{$center:[[56,80],50]}}},{_id:0,gis:1})