您的示例的问题基本上是数据确实“过于随机”,以便在这种情况下有效利用索引。结果与预期一致,因为索引如何遍历它并没有太多“顺序”,并且考虑到当您为文档中的每个字段编制索引时,索引大小将比文档本身大一些。
为了更好地表示“现实世界”的情况,您可以查看更多 50/50 的相关数据拆分以进行搜索。这里有一个更优化的生成器形式:
var samples = [{ "a": "a", "b": "a" },{ "a": "b", "b": "b" }];
for ( var x = 0; x < 5; x++ ) {
samples.forEach(function(s) {
var batch = [];
for(i = 0; i < 10000; i++){
batch.push( s );
}
db.test.insert(batch);
});
}
这会插入具有足够公平表示的数据,使得任一搜索本质上都必须扫描集合中的每个文档,以确定在没有索引的情况下检索它们。
因此,如果您现在使用表单查看查询以获取 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 },{ "_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-tree 索引并不是解决数据访问问题的最佳方式。
我希望这能让您更清楚地了解一些需要考虑的要点。
这是另一个更接近原始测试的样本,唯一的区别是改变“精度”,因此数据不是那么“随机”,这是我要提出的要点之一:
var batch = []
for( i = 0; i < 500000; i++){
batch.push({
"a": Math.round(Math.random()*100)/100,
"b": Math.round(Math.random()*100)/100,
"c": Math.round(Math.random()*100)/100,
"d": Math.round(Math.random()*100)/100,
"e": Math.round(Math.random()*100)/100
});
if ( batch.length % 10000 == 0 ) {
db.test.insert( batch );
batch = [];
}
}
因此,正在执行的数据中存在“两位小数精度”,这再次更直接地代表了现实世界的数据案例。另请注意,并非每次迭代都进行插入,因为 MongoDB 2.6 中 shell 的插入实现将在每次更新时返回“写关注”响应。设置起来要快得多。
如果您随后考虑您的原始测试查询,根据我的硬件,没有索引的响应将需要大约 590ms 才能完成。当您添加相同的索引时,查询将在 360ms 中完成。
如果你只在没有索引的“a”和“b”上这样做:
db.test.find({ "a": {"$gt": 0.5}, "b": {"$gt": 0.5} }).explain()
回复大约是490ms。只为“a”和“b”添加索引
db.test.ensureIndex({ "a": 1, "b": 1 })
索引查询大约需要300ms,因此速度仍然相当快。
这里的一切都在本质上说:
- B-tree 索引很好地支持自然分布,但不支持完全随机。
- 仅在这些字段上为您需要查询的内容编制索引。有大小成本,也有内存成本。
从第二点开始,还有一件事要演示,因为这里的大多数示例通常需要从集合中查找文档并在索引中找到它。这里明显的成本是索引和集合都需要分页到内存中才能返回结果。这当然需要时间。
考虑使用以下查询的完整复合索引,没有索引的响应大约需要485ms:
db.test.find({ "a": {"$gt": 0.5}, "b": {"$gt": 0.5} }).explain()
在“a”到“e”上添加复合索引会使相同的查询与原地索引在385ms周围运行。仍然更快,但比我们的完整查询慢,但考虑索引是有充分理由的包含所有字段和条件。但是,如果您只对必填字段进行投影来更改它:
db.test.find(
{ "a": {"$gt": 0.5}, "b": {"$gt": 0.5} },
{ "_id": 0, "a": 1, "b": 1 }
).explain()
这会稍微减少时间,现在只使用索引来获得结果。删除索引并发出相同的查询大约需要 650ms 以及投影的额外开销。这表明一个有效的指标实际上确实会对结果产生很大影响。