您的位置:首页 > 大数据 > 人工智能

使用NOSQL的MongoDB时建立索引需要注意的几点建议和Explain优化分析

2014-07-01 11:36 811 查看
http://www.itokit.com/2011/1207/72718.html

我们收到了很多关于索引的问题。这一部分解答了其中的一小部分。有几点要记住。

第一,MongoDB索引和MySQL索引非常相似并且对于MySQL的索引优化有很多也适用于MongoDB。

第二,更重要的是,这些索引的建议对你的应用提高也是有限的。

对于应用的最佳索引策略应该基于很多的重要因素。包含了你期望查询的类型,

数据读取与写入的比率,甚至于你服务器的空闲内存。意思就是,

需要对线上的产品做很多的测试剖析工作,才能调整出最佳的索引策略。

没有什么好的方法可以替代实际经验的。

注意: 如果你是个新手,建议阅读 read
this introductory article first.



索引策略


下面有些索引的基本法则

创建的索引要匹配查询。


如果你仅仅要查询单个字段。索引这个字段即可。如

db.posts.find({ slug : 'state-of-mongodb-2010' })


这个例子中,唯一索引是最佳的

db.posts.ensureIndex({ slug: 1 }, {unique: true});


然而,一般都查询多个键并且排序结果。这种情况,组合索引是最佳的,例子如下

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


创建的索引如下

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


要注意的是如果我们按照升序排序created_at。索引效率就低下了。

每个查询一个索引。


有的时候查询多个键,需要多个索引。在MongoDB中,这么做没问题。

如果你有个查询要匹配多个键,并且你想更有效地使用索引,请使用组合索引。

要确定所有的索引都在RAM中。


Shell中提供了一个查看索引大小的命令,如下:

db.comments.totalIndexSize();

65443


如果你的查询有点迟缓,你应该查看下索引是否都存入到RAM中了。

一个实例,如果你运行在4GB的RAM机器并且有3GB的索引,那么索引可能并不能全存在RAM中。

你需要添加RAM以及/或者校验实际的索引使用量。

要小心单键索引的低选择性。


假使你有个字段叫做'status',就有两个值new和processed。

如果在status上创建索引那么这就是个低选择性的索引。

意味着,查询中没有什么优势并且还占大量的空间。

一个更好一点的策略,当然依赖具体查询需求,可以创建组合索引包括这个低选择性的字段。

举例来说,你可以创建一个组合索引在status和created_at字段上。

另一个选择,当然也依赖你的需求,可以分离collection, 一个状态一个。

当然这些建议一定要进行测试,选择最优的方案。

使用explain.


MongoDB提供了一个explain命令,

用来查看查询的过程并且可以查看是否使用缩影。explain可以在驱动中使用,也可以在SHELL中使用:

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


返回了很多有用的信息。包含了检索的条数,消耗的毫秒数,优化器尝试的索引以及最终所使用的索引。

如果你从来没有使用过explain,请开始使用吧。

理解explain的输出.


explain输出主要有三个字段:

cursor: 游标不是 BasicCursor就是 BtreeCursor. 第二种意味着使用了索引。

nscanned: 扫描document的行数。

n: 查询返回的行数。你希望n的值和nsanned值接近。要避免做collection的扫描,

也就是访问所有的document。

millis: 查询完成的毫秒数。这个对于比较索引和非索引性能非常有用。


要关注应用读/写( read/write) 比率


这个很重要,因为,添加索引意味着添加,更新,删除操作变慢。

如果你的应用是偏向于读取,使用索引是非常好的事情。

但是如果你的应用偏向于写,那么创建索引就要小心了。增加索引都很影响写入的性能。

一般来说, 不要随便添加索引。索引应该按照你的查询来添加。

添加索引的理由总是很多的, 以及要进行大量的测试选择合适的索引策略。

索引特性


组合索引有许多特性要记住。

下面的例子都假想在 a, b, c上创建组合索引。因此创建索引语句如下

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



1. 排序的列一定要在索引的最后。


好的:

find(a=1).sort(a)

find(a=1).sort(b)

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

不好的:

find(a=1).sort(c)

即使c是索引的最后一列,a列是所使用的最后一列,因此你只能通过a或者b列进行排序。


2,3 ,4在1.6+已经不适用了。推荐使用1.6+以上版本。



5. MongoDB's $ne 或者$nin 操作在索因伤是无效的。


当要排除很少的documents。最好的方法就是在MongoDB查询出结果,在服务端进行排除。

=============


MongoDB范围查询的索引优化

http://blog.nosqlfan.com/html/4117.html

我们知道,MongoDB索引是B-Tree结构的,和MySQL的索引非常类似。所以你应该听过这样的建议:创建索引的时候要考虑到sort操作,尽量把sort操作要用到的字段放到你的索引后面。但是有的情况下,这样做反而会使你的查询性能更低。


问题

比如我们进行下面这样的查询:
db.collection.find({"country": "A"}).sort({"carsOwned": 1})


查询条件是 {“country”: “A”},按 carsOwned 字段的正序排序。所以索引就很好建了,直接建立 country , carsOwned 两个字段的联合索引即可。像这样:
db.collection.ensureIndex({"country": 1, "carsOwned": 1})


我们来看一个稍微复杂一点的查询:
db.collection.find({"country": {"$in": ["A", "G"]}}).sort({"carsOwned": 1})


这回我们是要查询 country 为 A 或者 G 的数据条目,结果同样按 carsOwned 字段排序。

如果我们还使用上面的索引,并且使用 explain() 分析一下这个查询,就会发现在输出中有一个“scanAndOrder” : true 的字段,并且 nscanned 的值可能会比想象中的大很多,甚至指定了 limit 也没什么效果。


原因

这是什么原因呢,我们先看下面这张图:



如上图所未,左边一个是按 {“country”: 1, “carsOwned”: 1} 的顺序建立的索引。而右边是按{“carsOwned”: 1, ”country”: 1} 顺序建立的索引。

如果我们执行上面的查询,通过左边的索引,我们需要将 country 值为A的(左图的左边一支)所有子节点以及country 值为G的(左图的右边一支)所有子节点都取也来。然后再对取出来的这些数据按 carsOwned 值进行一次排序操作。

所以说上面 explain 输出了一个 “scanAndOrder” : true 的提示,就是说这次查询,是先进行了scan获取到数据,再进行了独立的排序操作的。

那如果我们使用右边的索引来做查询,结果就不太一样了。我们没有将排序字段放在最后,而是放在了前面,相反把筛选字段放在了后面。那这样的结果就是:我们会从值为1的节点开始遍历(右图的左边一支),当发现有 country 值为 A 或 G 的,就直接放到结果集中。当完成指定数量(指定 limit 个数)的查找后。我们就可以直接将结果返回了,因为这时候,所有的结果本身就是按 carsOwned 正序排列的。

对于上面的数据集,如果我们需要2条结果。我们通过左图的索引需要扫描到4条记录,然后对4条记录进行排序才能返回结果。而右边只需要我们扫描2条结果就能直接返回了(因为查询的过程就是按需要的顺序去遍历索引的)。

所以,在有范围查询(包括$in, $gt, $lt 等等)的时候,其实刻意在后面追加排序索引通常是没有效果的。因为在进行范围查询的过程中,我们得到的结果集本身并不是按追加的这个字段来排的,还需要进行一次额外的排序才行。而在这种情况下,可能反序建立索引(排序字段在前、范围查询字段在后)反而会是一个比较优的选择。当然,是否更优也和具体的数据集有关。


总结

总结一下,举两个栗子。

当查询是:
db.test.find({a:1,b:2}).sort({c:1})


那么直接建立 {a:1, b:1, c:1} 或者 {b:1, a:1, c:1} 的联合索引即可。

如果查询是:
db.test.find({a:1,b:{$in:[1,2]}}).sort({c:1})


那么可能建立 {a:1, c:1, b:1} 的联合索引会比较合适。当然,这里只是提供了多一种思路,具体是否采用还是需要视你的数据情况而定。

来源:architects.dzone.com

=======================MongoDB与传统数据库的使用区别——批量插入与批量查询
http://blog.sina.com.cn/s/blog_56545fd301013zav.html

我在百X知道上回答问题时经常遇到类似与这样的问题:MongoDB有没有像MySQL一样的ODBC驱动?MongoDB能不能像MySQL一样获取字段名称或类型。

我的回答是:不行,因为MongoDB不是MySQL。这个回答显得MongoDB太弱了,我的原意是你不能要求一个物理优秀教师帮你辅导数学,也许他能做到基本的教学,但他很难做到优秀数学教师那么全面。

今天讨论的问题是:批量插入和批量查询

昨天在百X知道上有人问起MongoDB的批量插入如何写,这个我还真没用过,一方面MongoDB的速度足够快让我从来没有想过去找这种方法,另一方面MongoDB的官网以及API里也找不到这种方法。

那就带来两个问题。

问题1:这样岂不是没有速度更快的批量插入么?

这个问题毫无技术含量,MongoDB怎么可能会比MySQL慢?这里还是涉及到大家经常用到的传统关系型数据库和NoSQL的本质区别问题,NoSQL的每次操作都非常轻量级,小型化,除了数据的写入外基本没有多余的操作。再举个栗子:MongoDB就是放东西(数据)时把东西扔入相应的柜子(数据库)即可,而MySQL则要保持与送东西人的沟通(双向连接保持),东西的折叠整理分格存储(事务+有模式)。MySQL的批量插入就是减少了沟通以及分格等过程,而MongoDB本身就不存在这些过程,因此MongoDB就不存在批量插入这个概念了。结论就是,MongoDB的普通插入比MySQL的批量插入还要快,或者说MongoDB的普通插入就是批量插入。

问题2:把多个操作放入一个事务里一起执行不就不能实现了?

这个问题更没有技术含量了,MongoDB有事务么?还是那句,不要把NoSQL当关系型数据库用。那岂不是MongoDB的数据完整性和数据安全性会很差?这个,还得再重复一遍,MongoDB的设计是为了处理大规模数据的,所以对数据完整性要求不是那么严格。如果非要较真儿的话,MongoDB也可以处理这种情况,就是getLastError,它会牺牲性能以获取数据操作是否正确,你可以在批量插入一批数据后调用一次这个方法,如果出错,就把这批数据重新操作一遍,一批调用getLastError一次,既可保证性能,又可保证数据安全。

批量查询

再来说一下批量查询,这里的批量对应于官网上的batch select的概念,可以理解为一次查询一批数据。很多人在使用数据库的时候会用:

Statement stmt = a.createStatement();

ResultSet rs = stmt.executeQuery(sql);

for(int i = 1; i < 10000; i++){

//read data from rs

}

这样操作,会把数据库中的数据全部读入内存还是每条数据都去数据库中读一次?实际上两者都不是,MySQL会把部分数据放入内存,如果这部分数据读完了,那么再读入一部分。因为很久没用MySQL了,我记得C++的驱动中确实有一个类是用于把全部数据都读入内存的,不过这种方法很少人使用。

MongoDB的查询是这样的,你用Cursur去查询,如果没有设置batch
size这个参数,那么MongoDB默认会返回101条数据,等到这101条数据读完了,也就是说用户想读第102条数据,那么驱动会再次去MongoDB中获取后面的一批数据,这批数据不是以个数记的,而是最大限制4M的大小,将这4M的数据返回供用户继续读,读完再申请4M。当然,你可以通过batch
size来改变这一数值,如果设置了,那么每次返回都会返回batch size条数据。

转载请注明出处:http://blog.sina.com.cn/s/blog_56545fd301013zav.html

==============如何使用mongoose对一个100万+的mongodb的表进行遍历操作
http://cnodejs.org/topic/51508570604b3d512113f1b3


=====================

MongoDB优化的几点原则

http://nosqldb.org/topic/50cac0c8ee680fee79001566


1.查询优化

确认你的查询是否充分利用到了索引,用explain命令查看一下查询执行的情况,添加必要的索引,避免扫表操作。

2.搞清你的热数据大小

可能你的数据集非常大,但是这并不那么重要,重要的是你的热数据集有多大,你经常访问的数据有多大(包括经常访问的数据和所有索引数据)。使用MongoDB,你最好保证你的热数据在你机器的内存大小之下,保证内存能容纳所有热数据。

3.选择正确的文件系统

MongoDB的数据文件是采用的预分配模式,并且在Replication里面,Master和Replica
Sets的非Arbiter节点都是会预先创建足够的空文件用以存储操作日志。这些文件分配操作在一些文件系统上可能会非常慢,导致进程被Block。所以我们应该选择那些空间分配快速的文件系统。这里的结论是尽量不要用ext3,用ext4或者xfs。

4.选择合适的硬盘

这里的选择包括了对磁盘RAID的选择,也包括了磁盘与SSD的对比选择。

5.尽量少用in的方式查询,尤其是在shard上,他会让你的查询去被一个shand上跑一次,

如果逼不得已要用的话再每个shard上建索引。

优化in的方式是把in分解成一个一个的单一查询。速度会提高40-50倍

6.合理设计sharding key

increamenting sharding key(增量sharding-key)适合于可划分范围的字段,比如integer、float、date类型的,查询时比较快

random sharding key(随机sharding-key)适用于写操作频繁的场景,而这种情况下如果在一个shard上进行会使得这个shard负载比其他高,不够均衡,故而希望能hash查询key,将写分布在多个shard上进行

考虑复合key作为sharding key, 总的原则是查询快,尽量减少跨shard查询,balance均衡次数少。

mongodb默认是单条记录16M,尤其在使用GFS的时候,一定要注意shrading-key的设计。

不合理的sharding-key会出现,多个文档,在一个chunks上,同时,因为GFS中存贮的往往是大文件,导致mongodb在做balance的时候无法通过sharding-key来把这多个文档分开到不同的shard上,

这时候mongodb会不断报错

[conn27669] Uncaught std::exception: St9bad_alloc,
terminating。最后导致mongodb倒掉。

解决办法:加大chunks大小(治标),设计合理的sharding-key(治本)。

7.mongodb可以通过profile来监控数据,进行优化。

查看当前是否开启profile功能

用命令db.getProfilingLevel() 返回level等级,值为0|1|2,分别代表意思:0代表关闭,1代表记录慢命令,2代表全部

开启profile功能命令为

db.setProfilingLevel(level); #level等级,值同上

level为1的时候,慢命令默认值为100ms,更改为db.setProfilingLevel(level,slowms)如db.setProfilingLevel(1,50)这样就更改为50毫秒

通过db.system.profile.find() 查看当前的监控日志。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: