索引就是用来加速查询的。知道今后会做何种查询,以及哪些内容需要快速查找,这对于创建索引很重要。
索引简介
MongoDB的索引几乎与传统的关系型数据库索引一模一样。
创建索引要使用ensureIndex
方法:
> db.people.insert({"username":"mark"})
WriteResult({ "nInserted" : 1 })
> db.people.ensureIndex({"username":1})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
对于同一集合,同样的索引只需要创建一次。
对于某个键创建的索引会加速对该键的查询。然而,对于其他查询可能没有帮助。
> db.people.find().sort({"username":1})
{ "_id" : ObjectId("58b18e1f1d0fd17fd0b53294"), "username" : "make" }
{ "_id" : ObjectId("58b18dd11d0fd17fd0b53293"), "username" : "mark" }
> db.people.find().sort({"username":-1})
{ "_id" : ObjectId("58b18dd11d0fd17fd0b53293"), "username" : "mark" }
{ "_id" : ObjectId("58b18e1f1d0fd17fd0b53294"), "username" : "make" }
服务器必须“查找整本书”找到想要的日期。这个过程称为表扫描,就是在没有索引的书中找内容,要从第一页开始,从前翻到后。通常来说,要尽量避免让服务器做表扫描。
若索引只有一个键,则不需要考虑方向,若是有多个键,就需要考虑索引的方向问题。
创建索引的缺点就是每次插入、更新和删除时都会产生额外的开销。这是因为数据库不但需要执行这些操作,还要将这些操作在集合的索引中标记。因此,要尽可能少创建索引。每个集合默认的最大索引个数为64个。
一定不要索引每一个键,这会导致插入非常慢,还会占用很多空间。仔细考虑到底要做什么样的查询,什么样的索引适合这样的查询,通过explain和hint工具确保服务器使用了已建立的索引。
扩展索引
建立索引是要考虑如下问题:
- 会做什么样的查询?其中哪些键需要索引?
- 每个键的索引方向是怎样的?
- 如何应对扩展?有没有中不同的键的排列可以使常用数据更多地保留在内存中?
索引内嵌文档中的键
为内嵌文档的键创建索引和为普通的创建索引没有区别。
为排序创建索引
如果对没有索引的键调用sort,MongoDB需要将所有数据提取到内存来排序。一旦集合大到不能在内存中排序,MongoDB就会报错。 按照排序来索引以便让MongoDB按照顺序来提取数据,这样就能排序大规模数据,而不必担心用光内存。
索引名称
集合中的每个索引都有一个字符串类型的名字,来唯一表示索引,服务器通过这个名字来删除或者操作索引。
> db.foo.ensureIndex({"a":1, "b":1, "c":1}, {"name":"alphabet"})
索引名有字符个数的限制,所以特别复杂的索引在创建时一定要使用自定义的名字。可以用getLastError
来检查索引是否成功创建或者未成功创建的原因。
唯一索引
唯一索引可以确保集合的每一个文档的指定键都有唯一值。例如,如果想保证文档的“username”键都有不一样的值,创建一个唯一索引就好了。
> db.people.find()
{ "_id" : ObjectId("58b18dd11d0fd17fd0b53293"), "username" : "mark" }
{ "_id" : ObjectId("58b18e1f1d0fd17fd0b53294"), "username" : "make" }
{ "_id" : ObjectId("58b190431d0fd17fd0b53295"), "username" : "make" }
> db.people.ensureIndex({"username":1}, {"unique": true})
{
"ok" : 0,
"errmsg" : "E11000 duplicate key error collection: people.people index: username_1 dup key: { : \"make\" }",
"code" : 11000,
"codeName" : "DuplicateKey"
}
> db.people.remove({"username":"make"})
WriteResult({ "nRemoved" : 2 })
> db.people.ensureIndex({"username":1}, {"unique": true})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
消除重复
当为已有的集合创建索引,可能有些值已经有重复了。若是真的发生这种情况,那么索引的创建就是失败的。dropDups
选项就可以保留发现的第一个文档。而删除接下来的有重复值的文档:
> db.people.find()
{ "_id" : ObjectId("58b18dd11d0fd17fd0b53293"), "username" : "mark" }
{ "_id" : ObjectId("58b18e1f1d0fd17fd0b53294"), "username" : "make" }
{ "_id" : ObjectId("58b190431d0fd17fd0b53295"), "username" : "make" }
> db.people.dropIndexes()
{
"nIndexesWas" : 1,
"msg" : "non-_id indexes dropped for collection",
"ok" : 1
}
> db.people.ensureIndex({"username":1}, {"unique": true, "dropDups":true})
{
"ok" : 0,
"errmsg" : "E11000 duplicate key error collection: people.people index: username_1 dup key: { : \"make\" }",
"code" : 11000,
"codeName" : "DuplicateKey"
}
#dropDups不支持mongodb3版本
复合唯一索引
创建复合唯一索引的时候,单个键的值可以相同,只要所有键的值组合起来不同就好。 GridFS是MongoDB中存储大文件标准范式,其中就用到了复合唯一索引。
使用explain和hint
explain
是一个非常有用的工具,会帮助你获得查询方面诸多有用的信息,只要对游标调用该方法,就可以得到查询细节。explain
会返回一个文档,而不是游标本身。
> db.people.find().explain()
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "people.people",
"indexFilterSet" : false,
"parsedQuery" : {
},
"winningPlan" : {
"stage" : "COLLSCAN",
"direction" : "forward"
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "yang-K45DR",
"port" : 27017,
"version" : "3.4.2",
"gitVersion" : "3f76e40c105fc223b3e5aac3e20dcd026b83b38b"
},
"ok" : 1
}
explain
会返回查询使用的索引情况(如果有的话),耗时及扫描文档数的统计信息。
如果发现MongoDB用了非预期的索引,可以使用hint
强制使用某个索引。
多数情况下这种指定都没什么必要,MongoDB的查询优化器非常智能,会替你选择该用哪个索引。
索引管理
索引的元信息存储在每个数据库的system.indexes
集合中,这是一个保留集合,不能对其插入或者删除文档。操作只能通过ensureIndex
或者dropIndexes
进行。
system.indexes
集合包含每个索引的详细信息,同时system.namespaces
集合也含有索引的名字。如果查看这个集合,会发现每个集合至少有两个文档与之对应,一个对于集合本身,一个对应集合包含的索引。
一定要记住集合名和索引名不能超过127字节。
删除索引
> db.people.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "people.people"
},
{
"v" : 2,
"unique" : true,
"key" : {
"username" : 1
},
"name" : "username_1",
"ns" : "people.people"
}
]
> db.people.dropIndex({username:1})
{ "nIndexesWas" : 2, "ok" : 1 }
> db.people.dropIndexes() #删除全部索引
{
"nIndexesWas" : 1,
"msg" : "non-_id indexes dropped for collection",
"ok" : 1
}