`
kisa77
  • 浏览: 28210 次
  • 性别: Icon_minigender_1
  • 来自: 郑州
社区版块
存档分类
最新评论

MongoDB学习笔记之四 Indexes

阅读更多

四、索引

     基本

     在shell中,你可以通过使用 ensuerIndex() 函数来创建索引,可对某个特定的1个或多个字段索引,根据mongo手册中提供的例子,要对键值"j"进行索引,可执行下列命令

db.things.ensureIndex({j:1});
//参数1代表升序,相反的-1代表降序

    ensureIndex()函数只有在索引不存在的时候才会创建。跟mysql类似,访问一个有索引的字段要比访问一个未被索引的字段速度要快得多。可以执行 db.things.getIndexes();命令来查看things这个Collections下的所有索引。

 

     默认索引

     Mongo会为_id建立索引,_id比较特殊不能被删除,且值是唯一的。

 

    Embedded Keys (子键?不确定的翻译)

    使用mongo你甚至可以对一个子键进行索引,例如

db.things.ensureIndex({"address.city": 1})

 

    Document as Keys(索引一个document)

    索引字段可能是任意类型,包括documents:

db.factories.insert( { name: "xyz", metro: { city: "New York", state: "NY" } } );
db.factories.ensureIndex( { metro : 1 } );
// this query can use the above index:
db.factories.find( { metro: { city: "New York", state: "NY" } } );

    另外一种方法是创建混合索引:

db.factories.ensureIndex( { "metro.city" : 1, "metro.state" : 1 } );
// these queries can use the above index:
db.factories.find( { "metro.city" : "New York", "metro.state" : "NY" } );
db.factories.find( { "metro.city" : "New York" } );
db.factories.find().sort( { "metro.city" : 1, "metro.state" : 1 } );
db.factories.find().sort( { "metro.city" : 1 } )

    两种方法各有优缺点。第一种对整个document进行索引,排序是预定义的按插入BSON数据的先后升序排列。使用混合索引的话,可以混合正序和倒序。the query optimizer will then be able to use the index for queries on solely the first key(s) in the index too.(这句不知改如何理解)

 

    数组

    当一个被索引的值是数组时,MongoDB会索引这个数组中的每一个元素。更多信息在 multikey模块有详细说明

 

    Compound Keys Indexes (混合键索引)

    除了Single-key单键基本索引之外,Mongo也支持“多键”混合索引,跟单键索引不同的一点就是使用 ensureIndex()创建的时候填写多个键值

db.things.ensureIndex({j:1, name:-1});

    数字参数指定索引的方向,1为正序,-1为倒序。方向对单键索引和随机存不要紧,但如果你要执行分组和排序操作的时候,它就非常重要了。

 

    Unique Indexes (唯一索引)

    作用跟关系型数据库中的唯一索引一样,创建方法为

db.things.ensureIndex({firstname: 1}, {unique: true});

 

    Missing Keys

    用唯一索引保存文件到集合里时,会将所有缺失的索引键作为空值插入,因此缺失相同的索引键插入不同的文件是不可能的。

  db.things.ensureIndex({firstname: 1}, {unique: true});
  db.things.save({lastname: "Smith"});
  // Next operation will fail because of the unique index on firstname.
  db.things.save({lastname: "Jones"});
 

     Duplicate Values

     在一个存在重复值的键上是无法创建唯一索引的,如果仍要创立必须设置dropDups参数,这样做会留下第一个重复值,以后遇到的含有重复值的记录都会被删除。

db.things.ensureIndex({firstname : 1}, {unique : true, dropDups : true})
 

     在1.3.2之前的版本,数据库在创建索引的时候会处于锁定状态,其他操作暂时无法执行。

 

     Dropping Indexes

 

     删除某个Collection下的所有索引:

db.collection.dropIndexes();

 

  Geospatial Indexing

     MongoDB支持二维空间索引,这是设计时考虑到基于位置的查询。例如“找到离目标位置最近的N条记录”。可以有效地作为附加条件过滤。

     如果需要使用这种索引,应确定对象中存储的字段是子对象或数组,前两个元素为X,Y坐标(或者Y,X坐标,保持一致即可。it might be advisible to use order-preserving dictionaries/hashes in your client code, to ensure consistency),一些例子:

 
{ loc : [ 50 , 30 ] }
{ loc : { x : 50 , y : 30 } }
{ loc : { foo : 50 , y : 30 } }
{ loc : { lat : 40.739037, long: 73.992964 } }

 

   Creating the Index

 

db.places.ensureIndex( { loc : "2d" } )     //应该是固定格式

 

     默认的,Mongo假设你索引的是经度/维度,因此配置了一个从-180到180的取值范围,如果你想索引更多,可以指定该参数:

 

db.places.ensureIndex( { loc : "2d" } , { min : -500 , max : 500 } )
 

     上面的代码将衡量索引保证存入的值在-500到500的范围之内。一般来说geo索引仅限于正方形以内且不包括边界以以外的范围,不能再边界上插入值,比如使用上面的代码,点(-500,-500)是不能被插入的。

 

     目前为止,每个Collection只能建立一个geo索引

 

    Querying

      该索引可以被用来精确匹配:

 

db.places.find( { loc : [50,50] } )

 

     对于geo索引来说,更重要的是一个查询可以找到目标点附近的点,不必要精确匹配。

 

db.places.find( { loc : { $near : [50,50] } } )

 

     上面的一句将按离目标点(50,50)距离最近的100个点(距离倒序排列),如果想指定返回的结果个数,可以使用limit()函数,若不指定,默认是返回100个。

 

db.places.find( { loc : { $near : [50,50] } } ).limit(20)

 

    Compound Indexes

      Mongo空间索引可选的支持第二字段索引.如果想用坐标和其他属性同事作为条件查询,把这个属性也一同索引,其他属性带注释性的加入索引中可使过滤更快。

 

db.places.ensureIndex( { location : "2d" , category : 1 } );
db.places.find( { location : { $near : [50,50] }, category : 'coffee' } );

 

    geoNear Command

      虽然find()语法为查询的首选,Mongo也提供来了 geoNear 命令来执行相似的函数。geoNear命令有一个额外的好处是结果中返回距离目标点的距离,以及一些利于排除故障的信息。

> db.runCommand( { geoNear : "places" , near : [50,50], num : 10 } );
> db.runCommand({geoNear:"asdf", near:[50,50]})
{
        "ns" : "test.places",
        "near" : "1100110000001111110000001111110000001111110000001111",
        "results" : [
                {
                        "dis" : 69.29646421910687,
                        "obj" : {
                                "_id" : ObjectId("4b8bd6b93b83c574d8760280"),
                                "y" : [
                                        1,
                                        1
                                ],
                                "category" : "Coffee"
                        }
                },
                {
                        "dis" : 69.29646421910687,
                        "obj" : {
                                "_id" : ObjectId("4b8bd6b03b83c574d876027f"),
                                "y" : [
                                        1,
                                        1
                                ]
                        }
                }
        ],
        "stats" : {
                "time" : 0,
                "btreelocs" : 1,
                "btreelocs" : 1,
                "nscanned" : 2,
                "nscanned" : 2,
                "objectsLoaded" : 2,
                "objectsLoaded" : 2,
                "avgDistance" : 69.29646421910687
        },
        "ok" : 1
}

 

     上面的命令将返回10条距离点(50,50)最近的记录,loc字段由该collection的空间索引自动检测后决定。

     如果你想添加一条过滤条件,可以这样:

> db.runCommand( { geoNear : "places" , near : [ 50 , 50 ], num : 10,
... query : { type : "museum" } } );

 

    Bounds Queries

    v1.3.4版本以上

     $within 参数可以代替$near来查找一个形状之内结果。同时,也支持$box(矩形)和$center(圆环)

    想要查找一个一个矩形之内所有的点,必须制定该矩形的左下角和右上角坐标:

> box = [[40, 40], [60, 60]]
> db.places.find({"loc" : {"$within" : {"$box" : box}}})
 

  Multikeys

      Mongo提供了一个有趣的特性可以自动索引数组对象的值,一个好的例子就是文章标签,试想你要存储含有多个分类名称的文章标签

 

$ dbshell
> db.articles.save( { name: "Warm Weather", author: "Steve", 
                      tags: ['weather', 'hot', 'record', 'april'] } )
> db.articles.find()
{"name" : "Warm Weather" , "author" : "Steve" , 
 "tags" : ["weather","hot","record","april"] , "_id" : "497ce4051ca9ca6d3efca323"}
 

    可以很轻易的用一个查询得到这个数组中特定的值

 

> db.articles.find( { tags: 'april' } )
{"name" : "Warm Weather" , "author" : "Steve" , 
 "tags" : ["weather","hot","record","april"] , "_id" : "497ce4051ca9ca6d3efca323"}

 

     我们也可以对tag数组进行索引,对数组元素创建索引,数组中的每个元素就都会被索引。

 

> db.articles.ensureIndex( { tags : 1 } )
true
> db.articles.find( { tags: 'april' } )
{"name" : "Warm Weather" , "author" : "Steve" , 
 "tags" : ["weather","hot","record","april"] , "_id" : "497ce4051ca9ca6d3efca323"}
> db.articles.find( { tags: 'april' } ).explain()
{"cursor" : "BtreeCursor tags_1" , "startKey" : {"tags" : "april"} , 
 "endKey" : {"tags" : "april"} , "nscanned" : 1 , "n" : 1 , "millis" : 0 }
 

  Embedded object fields in an array

   此外同样的方法也可以用于字段内容是对象的情况

 

> db.posts.find( { "comments.author" : "julie" } )
{"title" : "How the west was won" , 
 "comments" : [{"text" : "great!" , "author" : "sam"},
               {"text" : "ok" , "author" : "julie"}],
 "_id" : "497ce79f1ca9ca6d3efca325"}
 

   Querying on all values in a given set

     通过使用$all查询选项, a set of values may be supplied each of which must be present in a matching object field.

 

 

> db.articles.find( { tags: { $all: [ 'april', 'record' ] } } )
{"name" : "Warm Weather" , "author" : "Steve" , 
 "tags" : ["weather","hot","record","april"] , "_id" : "497ce4051ca9ca6d3efca323"}
> db.articles.find( { tags: { $all: [ 'april', 'june' ] } } )
> // no matches
 

 

 

 

 

  Indexing Advice and FAQ

    Indexing Strategies

     下面使一些常见创建高效索引的策略

 

     如果经常查询单个字段,建立一个单键索引即可。

 

     还有常见的唯一索引

 

     然而一般都需要查询多个字段还有排序,这时组合索引是最合适的

 

db.comments.find({ tags : 'mongodb'}).sort({ created_at : -1 });

 

     建立合适的索引:

 

db.comments.ensureIndex({tags : 1, created_at : -1});

 

     注意:上面的代码,如果我们想按created_at正排序的话,会很低效。

     One index per query

     查询多个字段时有时被认为使用多个索引好一些,但是对于Mongo这并不合适。如果你有一个查询想得到多个字段而且还希望此查询使用一个高效的索引,使用组合键索引

    Make sure your indexes can fit in RAM.

     命令行下提供了一个命令来返回指定Collection的索引大小:

 

db.comments.totalIndexSize();
65443
 

     如果你的查询看起来很缓慢,就应该检测一下索引足够小以适合内存。举个例子,如果你运行一个4G内存的机器而你的索引有3G大小,这时候你的索引大小跟内存也许就不是很匹配,你需要添加内存或检测所有索引确定是不是所有都是游泳的。

     Be careful about single-key indexes with low selectivity.

    假设有一个字段名为'status',含有值'processeed'和'new',在status上建立一个索引的话,这个索引就是 低选择性的(low-selectivity),意味着改索引除了占用空间以外对于定位记录没有太大帮助。

 

    更好的方案,根据查询,创建一个包含有低选择性字段的混合索引,例如,你可以在'status'和'created_at'上创建混合索引。

 

    Another option, again depending on your use case, might be to use separate collections, one for each status. As with all the advice here, experimentation and benchmarks will help you choose the best approach.

 

   Use explain

   MongoDB包含一个解释语法explain(),可以显示查询语句是怎样被执行的。需要特别说明的,无论是否使用索引,explain()语法可用于shell和各种语言的驱动中。

 

db.comments.find({ tags : 'mongodb'}).sort({ created_at : -1 }).explain();
 

     该语句返回很多有用的信息,包括被扫描过的记录数目、以毫秒为单位的任务查询时间、查询优化器尝试的哪些索引以及最终采用的索引。

     Exp:

> db.nemok.find().explain()
{
        "cursor" : "BasicCursor",
        "startKey" : {

        },
        "endKey" : {

        },
        "nscanned" : 1017,
        "n" : 1017,
        "millis" : 0,
        "oldPlan" : {
                "cursor" : "BasicCursor",
                "startKey" : {

                },
                "endKey" : {

                }
        },
        "allPlans" : [
                {
                        "cursor" : "BasicCursor",
                        "startKey" : {

                        },
                        "endKey" : {

                        }
                }
        ]
}

    cursor:该值为BasicCursor 和 BtreeCursor其中的一个。第二个值代表此次查询使用了索引。

    nscanned:扫描的文档数目。

    n:查询返回的结果条数,n的值越接近nscanned的值越好,尽量避免一次查询扫描整个Collection。

    millis:返回此次查询消耗的毫秒数

 

   Pay attention to the read/write ratio of your application.

     这一点很重要,索引的优缺点,不再赘述。

     Indexing Properties

    下面是一些组合索引的性能问题,需要注意

     假设创建一个三个字段的组合索引:

 

db.foo.ensureIndex({a: 1, b: 1, c: 1})

 

     This information is no longer strictly correct in 1.6.0+; compound indexes can now be used to service queries where range or filter fields are used within the compound index, not just fields used from left to right. Please run explain to see how the compound index is used.

 

 

1. The sort column must be the last column used in the index.

Good:

  • find(a=1).sort(a)
  • find(a=1).sort(b)
  • find(a=1, b=2).sort(c)

Bad:

  • find(a=1).sort(c)
  • even though c is the last column used in the index, a is that last column used, so you can only sort on a or b.
2. The range query must also be the last column in an index. This is an axiom of 1 above.

Good:

  • find(a=1,b>2)
  • find(a>1 and a<10)
  • find(a>1 and a<10).sort(a)

Bad:

  • find(a>1, b=2)
3. Only use a range query or sort on one column.

Good:

  • find(a=1,b=2).sort(c)
  • find(a=1,b>2)
  • find(a=1,b>2 and b<4)
  • find(a=1,b>2).sort(b)

Bad:

  • find(a>1,b>2)
  • find(a=1,b>2).sort(c)
4. Conserve indexes by re-ordering columns used on equality (non-range) queries.

Imagine you have the following two queries:

  • find(a=1,b=1,d=1)
  • find(a=1,b=1,c=1,d=1)

A single index defined on a, b, c, and d can be used for both queries.
If, however, you need to sort on the final value, you might need two indexes

5. MongoDB's $ne or $nin operator's aren't efficient with indexes.
  • When excluding just a few documents, it's better to retrieve extra rows from MongoDB and do the exclusion on the client side.

FAQ

I've started building an index, and the database has stopped responding. What's going on? What do I do?

Building an index can be an IO-intensive operation, especially you have a large collection. This is true on any database system that supports secondary indexes, including MySQL. If you'll need to build an index on a large collection in the future, you'll probably want to consider building the index in the background, a feature available beginning with 1.3.2. See the docs on background indexing for more info.

As for the long-building index, you only have a few options. You can either wait for the index to finish building or kill the current operation (see killOp() ). If you choose the latter, the partial index will be deleted.

I'm using $ne or $nin in a query, and while it uses the index, it's still slow. What's happening?

The problem with $ne and $nin is that much of an index will match queries like these. If you need to use $nin, it's often best to make sure that an additional, more selective criterion is part of the query.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论
1 楼 zcq100 2011-08-12  
非常感谢楼主的分享

相关推荐

Global site tag (gtag.js) - Google Analytics