第二,更重要的是,这些索引的建立对你的应用提高也是有限的。
对于应用的最佳索引策略应该基于很多的重要因素。包含了你期望查询的类型,
数据读取与写入的比率,甚至于你服务器的空闲内存。意思就是,
需要对线上的产品做很多的测试剖析工作,才能调整出最佳的索引策略。
没有什么好的方法可以替代实际经验的。
下面有些索引的基本法则
如果你仅仅要查询单个字段。索引这个字段即可。如
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中,这么做没问题。
如果你有个查询要匹配多个键,并且你想更有效地使用索引,请使用组合索引。
Shell中提供了一个查看索引大小的命令,如下:
db.comments.totalIndexSize();
65443
如果你的查询有点迟缓,你应该查看下索引是否都存入到RAM中了。
一个实例,如果你运行在4GB的RAM机器并且有3GB的索引,那么索引可能并不能全存在RAM中。
你需要添加RAM以及/或者校验实际的索引使用量。
假使你有个字段叫做‘status‘,就有两个值new和processed。
如果在status上创建索引那么这就是个低选择性的索引。
意味着,查询中没有什么优势并且还占大量的空间。
一个更好一点的策略,当然依赖具体查询需求,可以创建组合索引包括这个低选择性的字段。
举例来说,你可以创建一个组合索引在status和created_at字段上。
另一个选择,当然也依赖你的需求,可以分离collection, 一个状态一个。
当然这些建议一定要进行测试,选择最优的方案。
db.comments.find({ tags : ‘mongodb‘}).sort({ created_at : -1 }).explain();
如果你从来没有使用过explain,请开始使用吧。
explain输出主要有三个字段:
这个很重要,因为,添加索引意味着添加,更新,删除操作变慢。
如果你的应用是偏向于读取,使用索引是非常好的事情。
但是如果你的应用偏向于写,那么创建索引就要小心了。增加索引都很影响写入的性能。
一般来说, 不要随便添加索引。索引应该按照你的查询来添加。
添加索引的理由总是很多的, 以及要进行大量的测试选择合适的索引策略。
组合索引有许多特性要记住。
下面的例子都假想在 a, b, c上创建组合索引。因此创建索引语句如下
db.foo.ensureIndex({a: 1, b: 1, c: 1})
好的:
不好的:
=============
我们知道,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} 的联合索引会比较合适。当然,这里只是提供了多一种思路,具体是否采用还是需要视你的数据情况而定。
我在百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++){
}
这样操作,会把数据库中的数据全部读入内存还是每条数据都去数据库中读一次?实际上两者都不是,MySQL会把部分数据放入内存,如果这部分数据读完了,那么再读入一部分。因为很久没用MySQL了,我记得C++的驱动中确实有一个类是用于把全部数据都读入内存的,不过这种方法很少人使用。
MongoDB的查询是这样的,你用Cursur去查询,如果没有设置batch size这个参数,那么MongoDB默认会返回101条数据,等到这101条数据读完了,也就是说用户想读第102条数据,那么驱动会再次去MongoDB中获取后面的一批数据,这批数据不是以个数记的,而是最大限制4M的大小,将这4M的数据返回供用户继续读,读完再申请4M。当然,你可以通过batch size来改变这一数值,如果设置了,那么每次返回都会返回batch size条数据。
第二,更重要的是,这些索引的建议对你的应用提高也是有限的。
对于应用的最佳索引策略应该基于很多的重要因素。包含了你期望查询的类型,
数据读取与写入的比率,甚至于你服务器的空闲内存。意思就是,
需要对线上的产品做很多的测试剖析工作,才能调整出最佳的索引策略。
没有什么好的方法可以替代实际经验的。
注意: 如果你是个新手,建议阅读 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中,这么做没问题。
如果你有个查询要匹配多个键,并且你想更有效地使用索引,请使用组合索引。
Shell中提供了一个查看索引大小的命令,如下:
db.comments.totalIndexSize();
65443
如果你的查询有点迟缓,你应该查看下索引是否都存入到RAM中了。
一个实例,如果你运行在4GB的RAM机器并且有3GB的索引,那么索引可能并不能全存在RAM中。
你需要添加RAM以及/或者校验实际的索引使用量。
假使你有个字段叫做‘status‘,就有两个值new和processed。
如果在status上创建索引那么这就是个低选择性的索引。
意味着,查询中没有什么优势并且还占大量的空间。
一个更好一点的策略,当然依赖具体查询需求,可以创建组合索引包括这个低选择性的字段。
举例来说,你可以创建一个组合索引在status和created_at字段上。
另一个选择,当然也依赖你的需求,可以分离collection, 一个状态一个。
当然这些建议一定要进行测试,选择最优的方案。
db.comments.find({ tags : ‘mongodb‘}).sort({ created_at : -1 }).explain();
如果你从来没有使用过explain,请开始使用吧。
explain输出主要有三个字段:
这个很重要,因为,添加索引意味着添加,更新,删除操作变慢。
如果你的应用是偏向于读取,使用索引是非常好的事情。
但是如果你的应用偏向于写,那么创建索引就要小心了。增加索引都很影响写入的性能。
一般来说, 不要随便添加索引。索引应该按照你的查询来添加。
添加索引的理由总是很多的, 以及要进行大量的测试选择合适的索引策略。
组合索引有许多特性要记住。
下面的例子都假想在 a, b, c上创建组合索引。因此创建索引语句如下
db.foo.ensureIndex({a: 1, b: 1, c: 1})
好的:
不好的:
=============
我们知道,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} 的联合索引会比较合适。当然,这里只是提供了多一种思路,具体是否采用还是需要视你的数据情况而定。
我在百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++){
}
这样操作,会把数据库中的数据全部读入内存还是每条数据都去数据库中读一次?实际上两者都不是,MySQL会把部分数据放入内存,如果这部分数据读完了,那么再读入一部分。因为很久没用MySQL了,我记得C++的驱动中确实有一个类是用于把全部数据都读入内存的,不过这种方法很少人使用。
MongoDB的查询是这样的,你用Cursur去查询,如果没有设置batch size这个参数,那么MongoDB默认会返回101条数据,等到这101条数据读完了,也就是说用户想读第102条数据,那么驱动会再次去MongoDB中获取后面的一批数据,这批数据不是以个数记的,而是最大限制4M的大小,将这4M的数据返回供用户继续读,读完再申请4M。当然,你可以通过batch size来改变这一数值,如果设置了,那么每次返回都会返回batch size条数据。
通过db.system.profile.find() 查看当前的监控日志.
转自:http://www.360sdn.com/MongoDB/2014/0815/4142.html
使用NOSQL的MongoDB时建立索引需要注意的几点建议和Explain优化分析
原文地址:http://blog.csdn.net/q114942784/article/details/42675645