• Mongodb索引


    引言

             从今年年初开始接触Mongodb,就一直被如何建立最合理的索引这个问题折磨着,没办法,应用中的筛选条件太复杂。而关于Mongodb索引方面的中文资料并不多,所以只能在google上找找资料,然后就匆忙的开始用了。成长很曲折,也充满了惊喜,结合最近读的《Mongodb实战》,这里把一些经验和大家分享一下。基础语法此处略过,可参见《Mongodb权威指南》。

    索引结构

    Mongodb中的索引,是按照B树结构来存储的。B树有2个特点:

    第一,它能用于多种查询,包括精确匹配(等于)、范围条件($in, $lt,$lte,$gt,$gte)、排序、前缀匹配和仅用索引的查询;

    第二,在添加和删除键的时候,B树仍然能保持平衡。

    索引类型

    1. 唯一性索引

    要设置唯一性索引,只需要在添加索引时设置 unique 选项即可:

    db.RRP_RPT_BAS.ensureIndex({ID:1},{unique:true});

    唯一索引,顾名思义,就是在集合中,每条文档的ID都是唯一的。当我们建立上述索引之后,如果再次插入同样ID的文档,则Mongodb会报异常,只有当我们使用安全模式执行插入操作时,才能获取到该异常。当然,我们有很多时候,是先有数据,然后再根据查询需要来建的索引,这个时候就可以ID有重复的,如果这个时候再在ID字段上建立唯一索引,会执行失败,那怎么样才能让Mongodb顺利建立索引,而又删掉重复的数据呢?这个时候需要设置 dropDups 属性。

    db.RRP_RPT_BAS.ensureIndex({ID:1},{unique:true,dropDups:true});

    注意:对于重复出现ID相同的文档,在ID字段上建立唯一索引时,Mongodb无法确定会保留哪一条。

    2. 稀疏索引

    建立稀疏索引,有利于帮助优化索引结构,减小索引的大小。

    索引默认都是密集型的,也就是说,在一个有索引的集合里,每个文档都会有对应的索引项,哪怕文档中没有被索引键也是如此。比如一个表示产品信息的集合Product. 该集合中有一个表示产品所属分类的键:catagory_ids 。假设你在该键上构建了一个索引。现在假设有些产品没有分配给任何分类,对于每个无分类的产品, catagory_ids 索引中仍会存在像这样的一个null项,以便于查询没有产品分类的产品信息。但是有两种情况使用密集型索引就不太方便。

    情况一:我们在设计Product集合时,在某个字段上url建立了唯一索引。假设产品在尚未分配url时就加入系统了,如果url上有唯一索引,而你希望插入多个 url 为空的产品信息,那么第一次插入会成功,但是手续会失败,因为索引里已经存在一个 url 为 null 的项了。

    情况二:比如有一个博客网站,支持匿名评论。文档结构如下:

    {
        ID:20121130102121222,
        TITLE:"Mongodb索引优化",
        AUTHOR:"JIEJIEP",
        COMMENTS:[
            {
                USERID:NULL,
                CONT:"文章一般",
                CMT_TIME:"2012-11-30"
            },
            {
                USERID:NULL,
                CONT:"有点意思",
                CMT_TIME:"2012-11-30"
            },
            {
                USERID:"jimmy",
                CONT:"神奇的Mongodb",
                CMT_TIME:"2012-11-30"
            }
        ]
    }

    集合中包含大量评论的用户ID为空的情况,如果我们在 COMMENTS.USERID 上建立一个索引,那么该索引中会存在大量的为null的索引项。这样就会增加索引的大小,在添加和删除COMMENTS.USERID 为null 的文档时也需要更新索引,这都会影响性能。而我们又很少会去查询 COMMENTS.USERID 为null  的情况,所以索引中保存为null的索引项意义不大。

    对于以上两种情况,我们建议使用稀疏索引。只需要设置 sparse 选项。

    db.RRP_RPT_BAS.ensureIndex({ID:1},{sparse:true});

    3. 多键索引

    就是我们经常说的复合索引,它包含多个键。我会结合实例重点讲一下这类索引。

    索引作用规则

    如果我们建立这样一个索引:db.coll.ensureIndex({a:1,b:1,c:1});

    那实际上可以利用的索引有: {a:1},{a:1,b:1},{a:1,b:1,c:1}

    比如:

    db.coll.find({a:123});

    db.coll.find({a:123,b:”xxxx”}) ,

    这2个查询都会利用 {a:1,b:1,c:1} 索引。

    那我们怎么知道哪个查询使用了什么索引呢,这就要借助 explain 工具了。

    现在我们有一个集合 txt_nws_bas,50W条数据 。包含键 id ,tit , cont, pub_dt, typ_code, fld_nation, fld_object 。

    我们建立一个这样一个索引:{ "TYP_CODE" : 1, "FLD_OBJ" : 1, "FLD_NATION" : 1 }

    > db.txt_nws_bas.find({TYP_CODE:1101,FLD_OBJ:1101}).sort({FLD_NATION:-1}).explain();
    {
            "cursor" : "BtreeCursor TYP_CODE_1_FLD_OBJ_1_FLD_NATION_1 reverse",              --BtreeCursor 表示查询使用了索引,否则为 BasicCursor
            "isMultiKey" : true,                                                                                                                                             --是否使用多键索引
            "n" : 8,                                                                                                                                                                        --返回条数
            "nscannedObjects" : 8,                                                                                                                                        --扫描文档条数
            "nscanned" : 8,                                                                                                                                                       --扫描索引数目
            "nscannedObjectsAllPlans" : 8,
            "nscannedAllPlans" : 8,
            "scanAndOrder" : false,                                                                                                                                      --为true则表示对查询结果进行了重新排序,而没有使用索引排序。这个参数很重要
            "indexOnly" : false,                                                                                                                                               --是否为覆盖索引查询
            "nYields" : 0,
            "nChunkSkips" : 0,
            "millis" : 0,                                                                                                                                                                --查询耗时,单位为毫秒,这个参数值越小,表示查询速度越快
            "indexBounds" : {
                    "TYP_CODE" : [
                            [
                                    1101,
                                    1101
                            ]
                    ],
                    "FLD_OBJ" : [
                            [
                                    1101,
                                    1101
                            ]
                    ],
                    "FLD_NATION" : [
                            [
                                    {
                                            "$maxElement" : 1
                                    },
                                    {
                                            "$minElement" : 1
                                    }
                            ]
                    ]
            },
            "server" : "bd130:27017"
    }

    接下来,我们来讨论一下,Mongodb查询优化器是如何来选择最合适的索引的。

    首先我们来说明一下Mongodb查询优化器选择理想索引的原则

    1. 避免 scanAndOrder。如果查询中包含排序,尝试使用索引进行排序

    2. 通过有效的索引约束来满足所有字段-尝试对查询选择器里的字段使用索引

    3. 如果查询包含范围查找或者排序,那么对于选择的索引,其中最后用到的键需能满足该范围查找或者排序。

    查询首次运行时,优化器会为每个可能有效适用于该查询的索引创建查询计划,随后并行运行这个计划,nscanned 值最低的计划胜出。优化器会停止那些长时间运行的计划,将胜出的计划保存下来,以便后续使用。

    最后,我们来介绍一下索引应用规则。(只讲多键索引)

    索引:{ "TYP_CODE" : 1, "FLD_OBJ" : 1, "FLD_NATION" : 1 }

    1. 精确匹配

    精确匹配第一个键、第一个和第二个键,或者第一、第二和第三个键。

    db.txt_nws_bas.find({TYP_CODE:1634});

    db.txt_nws_bas.find({TYP_CODE:1634,FLD_OBJ:1100});

    db.txt_nws_bas.find({TYP_CODE:1634,FLD_OBJ:1100,FLD_NATION:420});

    注意:

    db.txt_nws_bas.find({TYP_CODE:1634}).sort({FLD_NATION:1}); 无法使用索引排序,数据量大时,执行该操作会报错。

    > db.txt_nws_bas.find({TYP_CODE:1101}).sort({FLD_NATION:-1}).explain();
    Fri Nov 30 12:41:50 uncaught exception: error: {
            "$err" : "too much data for sort() with no index.  add an index or specify a smaller limit",
            "code" : 10128
    }

    2. 范围匹配

    db.txt_nws_bas.find({TYP_CODE:1634,FLD_OBJ:{“$in”:[1100,1101,1102]}});

    db.txt_nws_bas.find({TYP_CODE:1634,FLD_OBJ:1100}).sort({FLD_OBJ:1});

    db.txt_nws_bas.find({TYP_CODE:1634,FLD_OBJ:1100,FLD_NATION:{“$in”:[420,9000]}});

    db.txt_nws_bas.find({TYP_CODE:1634,FLD_OBJ:1100}).sort({FLD_NATION:1});

    db.txt_nws_bas.find({TYP_CODE:1634,FLD_NATION:420}).sort({FLD_NATION:1});

    db.txt_nws_bas.find({TYP_CODE:1634,FLD_OBJ:1100,FLD_NATION:{“$in”:[420,9000]}}).sort({FLD_NATION:1});

    注意:

    db.txt_nws_bas.find({TYP_CODE:1101,FLD_OBJ:{"$in":[1101,1102,1100]}}).sort({FLD_NATION:-1});该查询不会使用索引排序。

     

  • 相关阅读:
    手写RPC
    随机生成6位的字符串验证码,要求包含数字,大小写字母
    输出学习阶段目标
    吃货联盟
    判断是否为整数
    实现一个登录注册功能
    作业1
    年龄异常
    作业2
    作业1
  • 原文地址:https://www.cnblogs.com/jiejie_peng/p/2796046.html
Copyright © 2020-2023  润新知