zoukankan      html  css  js  c++  java
  • 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就可以知道索引是否创建成功,或者失败的原因。

  • 相关阅读:
    winform 监视DataGridView的滚动条,加载数据
    SQL 自动生成行号
    SQL 删除重复的数据(多个字段判断),只保留一条数据
    C# 一个简单的 工厂模式 例子
    winform 重写TextBox的OnTextChanged方法(事件)
    winform 给文本框加载内容的时候 始终让文本框的滚动条到底(允许显示多行Multiline=True)
    winform 制定DataGridViewTextColumn列(更改DataGridView的Cell的状态很有用)
    winform 使用委托 实现多线程访问控件
    C# 一个简单的递归函数和 两个List<T> 合并
    ADO.NET  把数据库A的某表的数据内容复制到数据库B的某表内
  • 原文地址:https://www.cnblogs.com/CraryPrimitiveMan/p/4522929.html
Copyright © 2011-2022 走看看