Performance 非常奇怪--添加复合索引会使查询速度慢得多(MongoDB)

Performance 非常奇怪--添加复合索引会使查询速度慢得多(MongoDB),performance,mongodb,indexing,Performance,Mongodb,Indexing,我遇到了一个应该很简单的问题,但我在这个问题上被难住了——也许我对MongoDB中的复合索引有些误解 为了重现这个问题,我创建了一个简单的集合,其中包含500000个条目和六个字段,每个字段都有一个随机数。在mongo终端中,我生成了如下集合: (i=0;i335ms 查询在335毫秒内完成。因此,现在我添加了一个复合索引,以加快查询速度: db.test.ensureIndex({a:1,b:1,c:1,d:1,e:1}) 现在查询应该更快,但运行完全相同的查询需要更长的时间: t1=new

我遇到了一个应该很简单的问题,但我在这个问题上被难住了——也许我对MongoDB中的复合索引有些误解

为了重现这个问题,我创建了一个简单的集合,其中包含500000个条目和六个字段,每个字段都有一个随机数。在
mongo
终端中,我生成了如下集合:

(i=0;i<500000;i++)的
{
db.test.save({a:Math.random(),b:Math.random(),c:Math.random(),d:Math.random(),e:Math.random()})
}
然后,我对该集合执行一个简单的查询,如下所示:

t1=new Date().getTime()
db.test.count({a:{$gt:0.5},b:{$gt:0.5},c:{$gt:0.5},d:{$gt:0.5},e:{$gt:0.5})
t2=新日期().getTime()
t2-t1
=>335ms

查询在335毫秒内完成。因此,现在我添加了一个复合索引,以加快查询速度:

db.test.ensureIndex({a:1,b:1,c:1,d:1,e:1})
现在查询应该更快,但运行完全相同的查询需要更长的时间:

t1=new Date().getTime()
db.test.count({a:{$gt:0.5},b:{$gt:0.5},c:{$gt:0.5},d:{$gt:0.5},e:{$gt:0.5})
t2=新日期().getTime()
t2-t1
=>762ms

添加索引时,相同的查询将占用两倍的时间!即使我多次尝试,这也是可以重复的。使用
db.test.dropIndexes()
删除索引会使查询再次运行得更快,返回到~350ms

使用
explain()
检查查询表明在添加索引之前使用了
BasicCursor
。添加索引后,将使用
BtreeCursor
,并具有预期的
indexBounds

所以我的问题是:为什么会发生这种情况?更重要的是,如何让这个查询运行得更快?在我在同一台机器上做的一个SQL基准测试中,使用SQL的类似查询在没有索引的情况下耗时约240毫秒,而索引将其降至约180毫秒

我的MongoDB版本信息:

>mongo——版本
MongoDB外壳版本:2.6.3

这里的示例的问题基本上是,为了在这种情况下有效地使用索引,中的数据确实“太随机”。结果正如预期的那样,因为在索引如何遍历这一点上没有太多的“顺序”,同时考虑到在为文档中的每个字段编制索引时,索引的大小将略大于文档本身

为了更好地表示“真实世界”的情况,您可以查看要搜索的相关数据的50/50比例。这里有一个更优化的生成器形式:

var-samples=[{“a”:“a”,“b”:“a”},{“a”:“b”,“b”:“b”}];
对于(变量x=0;x<5;x++){
samples.forEach(函数){
var批=[];
对于(i=0;i<10000;i++){
批量推送;
}
db.测试.插入(批次);
});
}
这将插入具有足够公平表示形式的数据,这两种搜索基本上都必须确定地扫描集合中的每个文档,以便在没有索引的情况下检索所有文档

因此,如果您现在查看一个带有表单的查询,以获取50%的数据:

db.test.find({a:1,“b:1}).explain()
在我坐的硬件上,即使是预热,也要持续超过100毫秒才能完成。但当您向两个字段添加索引时:

db.test.ensureIndex({“a”:1,“b”:1})
然后,相同的查询在
100ms
下持续完成,并且大部分在
90ms
标记附近完成。当您添加一些投影以强制统计数据为“仅索引”时,这也会变得更加有趣:

db.test.find({a:1,b:1},{U id,a:1,b:1}).explain()
现在,虽然在这种情况下不需要返回到文档,并且标记为
“indexOnly”:true
,但工作集大小可能足够小,足以容纳内存,因此您会看到由于“投影”字段的额外工作而导致的性能轻微下降。在硬件上,现在索引的平均值约为
110ms
。但当您删除索引时:

db.test.dropIndexes()
不使用索引的查询性能下降到
170ms
。这更清楚地显示了该指数带来的收益的预测开销

将索引拉回到原来的表单:

db.test.ensureIndex({a:1,b:1,c:1,d:1,e:1})
保持相同的投影查询,您可以使用索引使用
135ms
,当然也可以使用相同的
170ms
。现在,如果返回原始查询表单:

db.test.find({a:1,b:1,c:1,d:1,e:1}).explain()
带有索引的结果仍然在
135ms
标记附近,而非索引查询在
185ms
标记附近跳过

因此,现实世界中的数据分布并不像您设计的测试那样“随机”,这是有道理的。虽然分布几乎从来没有像50/50那样清晰,但一般情况下,事实上没有这么多的分散,而且往往会有你正在寻找的范围的自然集群

这也是一个例子,对于值之间具有高度分布的“真正随机”数据,b树索引不是解决数据访问问题的最佳方法

我希望这能让你更清楚地考虑一些问题。


这是另一个与原始测试更接近的样本,唯一的区别是改变了“精度”,因此数据不是那么“随机”,这是我的主要观点之一:

var批处理=[]
对于(i=0;i<500000;i++){
批处理推送({
“a”:Math.round(Math.random()*100)/100,
“b”:Math.round(Math.random()*100