你的位置:首页 > 数据库

[数据库]MongoDB的学习


索引可以用来优化查询,而且在某些特定类型的查询中,索引是必不可少的。为集合选择合适的索引是提高性能的关键。

先来mock数据

for (i = 0; i < 1000000; i++) {  db.users.insert({    "i": i,    "username": "user" + i,    "age": Math.floor(Math.random() * 120),    "created": new Date()  });}

数据库中会创建一百万条数据,稍微有点慢,需要等会。

我们可以使用explain()函数查看MongoDB在执行查询的过程中所做的事情。执行如下命令,查找用户名为user1000的用户。

db.users.find({username:"user1000"}).explain()

得到结果如下:

{  "cursor" : "BasicCursor",  "isMultiKey" : false,  "n" : 1,  "nscannedObjects" : 1000000,  "nscanned" : 1000000,  "nscannedObjectsAllPlans" : 1000000,  "nscannedAllPlans" : 1000000,  "scanAndOrder" : false,  "indexOnly" : false,  "nYields" : 7813,  "nChunkSkips" : 0,  "millis" : 411,  "server" : "user:27017",  "filterSet" : false}

之后会详细介绍各个字段的意思,现在我们只需要知道,"n"表示查询结果的数量,"nscanned"表示MongoDB在完成这个查询的过程中扫描的文件总数,"millis"表示这个查询耗费的毫秒数。可以看到,为了查找user1000,MongoDB遍历了整个集合,消耗了411毫秒。

为了优化查询,我们可以在查找到一个结果的时候,就结束查询,返回结果。命令如下:

db.users.find({username:"user1000"}).limit(1).explain()

结果如下:

{  "cursor" : "BasicCursor",  "isMultiKey" : false,  "n" : 1,  "nscannedObjects" : 1001,  "nscanned" : 1001,  "nscannedObjectsAllPlans" : 1001,  "nscannedAllPlans" : 1001,  "scanAndOrder" : false,  "indexOnly" : false,  "nYields" : 7,  "nChunkSkips" : 0,  "millis" : 1,  "server" : "user:27017",  "filterSet" : false}

可以看到扫描文档数和消耗时间都变少了很多,但是如果我们要查找user999999,MongoDB还是要遍历集合才能找到。而且随着用户数量的增多,查询会越来越慢。

对于这种情况,创建索引是一个非常好的解决方案:索引可以根据给定的字段组织数据,让MongoDB能够非常快速的找到目标文档。使用如下命令,在username字段上创建一个索引。

db.users.ensureIndex({"username":1})

然后再来执行一下之前执行过的语句

db.users.find({username:"user1000"}).explain()

其结果如下:

{  "cursor" : "BtreeCursor username_1",  "isMultiKey" : false,  "n" : 1,  "nscannedObjects" : 1,  "nscanned" : 1,  "nscannedObjectsAllPlans" : 1,  "nscannedAllPlans" : 1,  "scanAndOrder" : false,  "indexOnly" : false,  "nYields" : 0,  "nChunkSkips" : 0,  "millis" : 0,  "indexBounds" : {    "username" : [      [        "user1000",        "user1000"      ]    ]  },  "server" : "user:27017",  "filterSet" : false}

然后你会发现查询变快了很多,几乎是瞬间完成,这就是使用索引的效果。但是索引也是有代价的,对于添加的每一个索引,每次写操作(插入、更新、删除)都将耗费更多的时间。这是因为当数据发生变动时,MongoDB不仅要更新文档,还要更新集合上的所有索引。因此,MongoDB限制每个集合上最多只能有64个索引。通常,在一个特定的集合上,不应该拥有两个以上的索引。

 当一个索引建立在多个字段上时,我们称它为复合索引,创建的语句如下:

db.users.ensureIndex({"age":1, "username":1})

 如果查询中有多个排序方向或者查询条件中有多个键,复合索引就会非常有用。

MongoDB对这个索引的使用方法取决于查询的类型。下面是三种主要的方式。

第一种:

db.users.find({"age":21}).sort({"username":-1})

这是一个点查询,用于查找单个值(尽管包含这个值的文档是多个)。由于索引中的第二个字段,查询结果已经是有序的了。这种类型的查询是非常高效的。

第二种:

db.users.find({"age":{"$gte":21,"$lte":30}})

这是一个多值查询,查找到多个值相匹配的文档,MongoDB会使用索引中的第一个键“age”得到匹配文档。如果使用“username”做查询,该索引不起作用。

第三种:

db.users.find({"age":{"$gte":21,"$lte":30}}).sort({"username":1})

这也是一个多值查询,与上一个类似,只是这次需要对查询结果进行排序。MongoDB需要在内存中对结果进行排序,不如上一个高效。

删除索引的命令如下:

db.users.dropIndex('age_1_username_1')

删除users集合中名字为'age_1_username_1'的索引。

所有数据库的索引信息都存储在system.indexes集合中,这是一个保留集合,不能在其中插入或者删除文档,只能通过ensureIndex和dropIndex对其进行操作。

使用如下命令可以获取users集合上的索引信息:

db.users.getIndexes()

结果如下:

[  {    "v" : 1,    "key" : {      "_id" : 1    },    "name" : "_id_",    "ns" : "test.users"  },  {    "v" : 1,    "key" : {      "username" : 1    },    "name" : "username_1",    "ns" : "test.users"  },  {    "v" : 1,    "key" : {      "age" : 1,      "username" : 1    },    "name" : "age_1_username_1",    "ns" : "test.users"  }]