目录
1.普通索引
索引是加速数据库查询的重要工具,只有在数据量比较大的时候才有意义,所以向数据库中批量插入20000条数据:
> for(i=0;i<20000;i++){
... db.numbers.save({num:i});
... }
WriteResult({ "nInserted" : 1 })
> db.numbers.count()
20000
执行后需要稍微等待几秒钟
做一次查询并explain(显示查询计划),结果类似下面:
> db.numbers.find({num:{"$gt":19995}}).explain("executionStats")
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.numbers",
"indexFilterSet" : false,
"parsedQuery" : {
"num" : {
"$gt" : 19995
}
},
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"num" : {
"$gt" : 19995
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 4,
"executionTimeMillis" : 10,
"totalKeysExamined" : 0,
"totalDocsExamined" : 20000,
"executionStages" : {
"stage" : "COLLSCAN",
"filter" : {
"num" : {
"$gt" : 19995
}
},
"nReturned" : 4,
"executionTimeMillisEstimate" : 11,
"works" : 20002,
"advanced" : 4,
"needTime" : 19997,
"needYield" : 0,
"saveState" : 156,
"restoreState" : 156,
"isEOF" : 1,
"invalidates" : 0,
"direction" : "forward",
"docsExamined" : 20000
}
},
"serverInfo" : {
//这里是一些本机信息
},
"ok" : 1
}
explain函数可以接受的参数有:'queryPlanner'(等于无参), 'executionStats', 'allPlansExecution'
这里可以看到,本次查询需要遍历全部20000个文档(docsExamined),耗时大约10ms,在真实环境中,NoSQL数据库存储的数据量级远远大于2W,假如200W数据,那就要1s,显然不可接受
1)索引建立、删除
可以使用createIndex函数,在这上面建立索引:
> db.numbers.createIndex({num:1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.numbers.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.numbers"
},
{
"v" : 2,
"key" : {
"num" : 1
},
"name" : "num_1",
"ns" : "test.numbers"
}
]
num:1代表在num域建立索引,使用getIndexes()查询可得,目前有两个索引,一个关联在_id,一个关联在num,如果要删除索引,可以使用dropIndex函数,用法和createIndex一致,不过不能删除_id上的索引。
也可以用ensureIndex命令建立索引,不过更建议使用createIndex,这个API更新。
createIndex函数也可以接受选项:
参数 | 类型 | 描述 |
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 |
dropDups | Boolean | 在建立唯一索引时是否删除重复记录,指定 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. |
需要注意的是,如果要建立唯一索引,最好在插入数据前就进行,否则可能会出现冲突。
重新explain刚刚的查询,"executionStages"一节如下:
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 4,
"executionTimeMillisEstimate" : 0,
"works" : 5,
"advanced" : 4,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 4,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 4,
"executionTimeMillisEstimate" : 0,
"works" : 5,
"advanced" : 4,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"num" : 1
},
"indexName" : "num_1",
... //剩下的暂时不重要
}
}
可以看到,自动使用了新建立的索引,仅仅检查了4个文档(docsExamined),且用时为0ms,性能不知道高到哪里去
如果此时想强制使用_id索引,可以使用hint()函数:
> db.numbers.find({num:{"$gt":19995}}).hint({_id:1}).explain("executionStats")
... //省略
"needTime" : 19996,
"needYield" : 0,
"saveState" : 156,
"restoreState" : 156,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 20000,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 20000,
"executionTimeMillisEstimate" : 10,
"works" : 20001,
"advanced" : 20000,
"needTime" : 0,
"needYield" : 0,
"saveState" : 156,
"restoreState" : 156,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"_id" : 1
},
"indexName" : "_id_",
... //省略
可以看到,此时索引变成了_id,且检查了20000个对象才完成查询
在数据发生变化(例如插入新数据)之后,索引会重新建立,也可以使用reIndex()函数显式地重建索引
MongoDB也支持哈希索引:
> db.user.createIndex({name:"hashed"})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
上面的命令是在name字段上建立了哈希索引。所谓哈希索引,就是先对字段的值取哈希,再根据哈希值进行索引,因此有以下限制:
- 不支持范围查询
- 不允许多键哈希索引
- 浮点数会先转换成整数再取哈希,例如:4.2与4.3的哈希索引值是相同的
哈希索引的一大用处是进行数据分片,MongoDB默认使用id作为索引,但是其值相似性很高,分片时容易出现集聚,如果使用哈希索引,就能保证均衡。
MongoDB的索引是建立在内存中的,在我本机测试时,上述索引建立后,mongod进程使用的内存瞬间增加了12KB
和MySQL等数据库一样,MongoDB的索引的数据结构也是B树。在MongoDB中,每个结点分配了8KB空间(一般保持60%满),每个key最大1KB,不过每个key还有18B的额外空间开销,每个结点还有40B的额外开销,因此结点可能有一些未使用的空白空间。
2)覆盖索引查询
假如在该集合的查询中,显式将_id字段排除,那么此时就发生了覆盖索引查询:
- 所有的查询字段是索引的一部分
- 所有的查询返回字段在同一个索引中
覆盖查询直接从索引中查询数据,由于索引存放在内存中,查询起来会更快
3)索引失效
和其他数据库类似,某些情况下索引无法命中:
- 查询条件包含正则表达式及非操作符,如 $nin, $not, 等。
- 查询条件包含算术运算符,如 $mod, 等。
- 查询条件包含$where 子句
> db.numbers.find({num:{$mod:[100,0]}},{num:1}).explain("executionStats")
... //省略
"docsExamined" : 200,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"filter" : {
"num" : {
"$mod" : [
100,
0
]
}
},
"nReturned" : 200,
"executionTimeMillisEstimate" : 10,
"works" : 20001,
"advanced" : 200,
"needTime" : 19800,
"needYield" : 0,
"saveState" : 156,
"restoreState" : 156,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"num" : 1
},
"indexName" : "num_1"
... //省略
可以看到,使用$mod操作符时,尽管使用了索引,但是无法命中,仍旧消耗了较长时间
4)索引限制
mongoDB的索引存放在内存里,因此有一定限制:
- 索引键限制:索引项的总大小必须小于1024字节(可能包含了数据结构元数据信息的开销)
- 单个集合的索引不能超过64个
- 包含命名空间和点分隔符(即<database name>.<collection name>.$<index name>)的完全限定索引名不能长于128个字符。
- 复合索引中的字段不能超过32个
- 索引不能覆盖对数组字段的查询
- 索引不能超过剩余内存空间大小,否则MongoDB会尝试清除已有的索引以释放空间,这会导致查询变慢
其他限制请参阅官方文档
2.全文索引
全文检索的原理是,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并返回查找的结果。这个过程类似于通过字典中的检索字表查字的过程。
1)索引建立
建立的方法和普通索引一样,只是把1换成"text",用以前建立的user集合来测试:
> db.user.createIndex({"hobby":"text"})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
如果使用的是2.6以前的版本,需要显式开启全文索引支持:
db.adminCommand({setParameter:true,textSearchEnabled:true})
或者使用命令:mongod --setParameter textSearchEnabled=true
和普通索引一样,全文索引也可以在多个字段上建立。如果是想对所有字符串字段建立全文索引,可以使用:db.collection_name.createIndex({'$**':'text'})
这里有一个权重概念,即进行全文查询的时候,哪个字段上的索引更重要:
> db.user.createIndex({'$**':'text'},{weights:{name:10,sex:5,hobby:3}})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
权重越大越重要。
2)全文查询
然后就可以进行查询了:
> db.user.find({$text:{$search:'programming book'}})
{ "_id" : ObjectId("5c3ef0037da85af675c7c109"), "name" : "wangwu", "sex" : "man", "age" : 18, "hobby" : "read book" }
{ "_id" : ObjectId("5c3eef6d7da85af675c7c107"), "name" : "zhangsan", "sex" : "man", "age" : 21, "hobby" : "programming" }
要使用$text和$search两个操作符
3)复杂全文查询
不过上述查询有个问题,我们查询的是programming book,但结果却是一个包含programming,一个包含book,即默认为or查询。如果指定必须包含在结果中的关键字,可以用双引号包裹住:
> db.user.find({$text:{$search:'programming "book"'}})
{ "_id" : ObjectId("5c3ef0037da85af675c7c109"), "name" : "wangwu", "sex" : "man", "age" : 18, "hobby" : "read book" }
这次的查询结果只包含book了。如果对每个词都使用双引号,结果就相当于and查询:
> db.user.find({$text:{$search:'"programming" "book"'}})
>
可以看到,没有同时包含这两个词的文档,所以没有查询结果。
假如要排除指定关键词,可以用负号:
> db.user.find({$text:{$search:'programming -"book"'}})
{ "_id" : ObjectId("5c3eef6d7da85af675c7c107"), "name" : "zhangsan", "sex" : "man", "age" : 21, "hobby" : "programming" }
这次的查询结果变成了不包含book,且包含programming的文档。
全文查询有一个匹配度的概念,查看匹配度可以用$meta操作符:
> db.user.find({$text:{$search:'programming book'}},{score:{$meta:"textScore"}})
{ "_id" : ObjectId("5c3eef6d7da85af675c7c107"), "name" : "zhangsan", "sex" : "man", "age" : 21, "hobby" : "programming", "score" : 1 }
{ "_id" : ObjectId("5c3ef0037da85af675c7c109"), "name" : "wangwu", "sex" : "man", "age" : 18, "hobby" : "read book", "score" : 0.75 }
匹配度可以大于1。配合sort和limit,我们就可以筛选出最匹配的top-k个结果了。
4)多语言全文索引
全文索引建立时,可以手动指定索引的语言,默认英语
> db.user.createIndex({'$**':'text'},default_language:'french')
该语句就是建立了一个基于法语的全文索引:
> db.user.getIndexes()
[
……
"name" : "$**_text",
"ns" : "test.user",
"weights" : {
"$**" : 1,
"hobby" : 3,
"name" : 10,
"sex" : 5
},
"default_language" : "french",
"language_override" : "language",
"textIndexVersion" : 3
}
]
查询时,可以用$language操作符指定查询语言:
> db.user.find({$text:{$search:'book',$language:'french'}})
{ "_id" : ObjectId("5c3ef0037da85af675c7c109"), "name" : "wangwu", "sex" : "man", "age" : 18, "hobby" : "read book" }
需要注意一个停止词的概念,MongoDB中,如果遇到停止词,就不会对所在文档进行索引:
> db.user.find({$text:{$search:'de'}})
{ "_id" : ObjectId("5c5c2d13cc53c46e01541e01"), "name" : "de", "sex" : "man", "age" : 10, "hobby" : "none" }
> db.user.find({$text:{$search:'de',$language:'french'}})
>
可以看到,由于de是法语的停止词,导致没有查询结果
全文索引支持的语言有:danish丹麦语、dutch荷兰语、english英语、finnish芬兰语、french法语、german德语、hungarian匈牙利语、italian意大利语、norwegian挪威语、portuguese葡萄牙语、romanian罗马尼亚语、russian俄语、spanish西班牙语、swedish瑞典语、turkish土耳其语