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"
    }
]

 集合中的每一个索引都有一个名称,用于唯一标识这个索引,也可以用于服务器端来删除或者操作索引。索引的默认形式是keyname1_dir1_keyname2_dir2_..._keynameN_dirN,其中keynameX是索引的键,dirX是索引的方向(1或者-1)。如果索引中包含两个以上的键,这种命名方式就显得比较笨重了,但是我们可以通过ensureIndex指定索引的名称:

db.users.ensureIndex({"a":1, "b":1, ..., "z":1},...{"name":"test_name"})

另外需要注意索引的名称长度是有限制的,所以新建复杂索引时可能需要自定义索引名称。调用getLastError就可以知道索引是否创建成功,或者失败的原因。

原文地址:https://www.cnblogs.com/CraryPrimitiveMan/p/4522929.html