在MongoDB中获取文档子集有效吗?

3
如果我们有一个集合photos,每个条目都是一个包含所有关于照片信息(包括浏览次数详细信息和详细的赞成/反对票)的大型文档。
{
_id:ObjectId('...'),
title:'...',
location:'...',
views:[
    {...},
    {...},
    ...
    ],
upvotes:[
    {...},
    {...},
    ...
    ],
downvotes:[
    {...},
    {...},
    ...
    ],
}

哪种查询方式更快、更有效(内存、CPU使用):
db.photos.find().limit(100)

或者

db.photos.find({}, {views:0,upvotes:0,downvotes:0}).limit(100)

?


如果您不需要它,最好省略额外的数据,因为它不需要被分页,而且它也不需要通过网络进行传输,这意味着游标的批量大小可以更大,一次发送更多的数据,从而在网络上创建更少的流量。 - Sammaye
@Sammaye 是的,但是有人告诉我每当您执行像第二个请求这样的请求时,它首先会获取所有有关结果的信息,然后才对结果运行mapReduce。 这意味着1)这样的请求将需要更长的时间,并且2)将需要更多的内存,因为所有结果将首先完全加载。 - Alexey Kamenskiy
1
第二个操作会比较慢,因为你使用了投影。这是一种更高CPU使用率的操作。 - Dmitry Zagorulkin
1
除非您指定要运行MR,否则永远不会运行MR,否则此查询将在MongoDB的C++代码中完成,MR在JS引擎内运行,这是完全不同的事情。 - Sammaye
@ZagorulkinDmitry,谢谢。这就是我在寻找的答案类型。从性能上讲,您建议使用第一个查询吗? - Alexey Kamenskiy
2个回答

5
实际上,这个故事有两面,应用程序和服务器。
在应用程序中,第二个速度将更快。应用程序不必反序列化BSON文档(CPU密集型),然后存储不需要的数据哈希(内存密集型)。
在服务器上,MongoDB可以发送更多的数据,允许在执行getMore操作之前对每个游标进行更多次迭代,从而提高性能。不仅如此,你当然会发送更少的数据。getMore操作本身实际上是资源密集型的,无论是对内存还是CPU都是一种节省。
至于服务器内部,投影的成本很小,但比把所有内容都传输过去的成本要小。
编辑
正如其他人所说,MongoDB实际上使用投影来操纵结果集,因此两个查询之间将具有相同的工作集。
编辑
这是关于投影索引使用的结果:
> db.g.insert({a:1,b:1,c:1,d:1})
> db.g.ensureIndex({ a:1,b:1,c:1 })
> db.g.find({}, {a:0,b:0,c:0}).explain()
{
        "cursor" : "BasicCursor",
        "nscanned" : 3,
        "nscannedObjects" : 3,
        "n" : 3,
        "millis" : 0,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : false,
        "indexOnly" : false,
        "indexBounds" : {

        }
}
> db.g.find({}, {a:1,b:1,c:1}).explain()
{
        "cursor" : "BasicCursor",
        "nscanned" : 3,
        "nscannedObjects" : 3,
        "n" : 3,
        "millis" : 0,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : false,
        "indexOnly" : false,
        "indexBounds" : {

        }
}

这也是不使用投影的结果:

> db.g.find({}).explain()
{
        "cursor" : "BasicCursor",
        "nscanned" : 3,
        "nscannedObjects" : 3,
        "n" : 3,
        "millis" : 0,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : false,
        "indexOnly" : false,
        "indexBounds" : {

        }
}

正如您所看到的,milis 表示文档上花费的时间实际上在两者之间是相同的:0。因此,解释不是衡量这个的好方法。

另一个编辑

除了 _id 外,不包括其他字段不会使覆盖索引生效:

> db.g.find({}, {a:1,b:1,c:1,_id:0}).explain()
{
        "cursor" : "BasicCursor",
        "nscanned" : 3,
        "nscannedObjects" : 3,
        "n" : 3,
        "millis" : 0,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : false,
        "indexOnly" : false,
        "indexBounds" : {

        }
}

又一次编辑

并且有300K行:

> db.g.find({}, {a:1,b:1,c:1}).explain()
{
        "cursor" : "BasicCursor",
        "nscanned" : 300003,
        "nscannedObjects" : 300003,
        "n" : 300003,
        "millis" : 95,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : false,
        "indexOnly" : false,
        "indexBounds" : {

        }
}

> db.g.find({}).explain()
{
        "cursor" : "BasicCursor",
        "nscanned" : 300003,
        "nscannedObjects" : 300003,
        "n" : 300003,
        "millis" : 85,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : false,
        "indexOnly" : false,
        "indexBounds" : {

        }
}

所以说,在一个庞大的结果集上进行投影操作确实更加耗费资源,但请记住这是在30万行数据上进行的投影操作……我是说,这有点离谱吧?谁会在理智的情况下这么做呢?因此,这部分争论实际上并不存在。无论如何,在我的硬件上,差异只有10毫秒,几乎只是你查询时间的1/10,因此投影操作并不是你的问题。

我还应该指出,--cpu标志将不能给你想要的结果,首先它实际上涉及写锁,其次你正在进行读取操作。


你认为为什么在投影中使用hint()会增加资源开销? - Dmitry Zagorulkin
@ZagorulkinDmitry 因为hint()强制加载索引,即使它不能用于查询。换句话说,您将一个索引加载到工作集中,但无法用于完整的表扫描查询。 - Sammaye
要使用覆盖索引,您需要明确排除_id字段。例如:db.g.find({}, {a:1,b:1,c:1,_id:0}),因为_id字段通常始终包含在内。另外,我认为您无法从比较仅有3个文档的集合的查询中得出任何有意义的性能结论。几十万个文档更有可能暴露性能差异。 - chrisbunney

3
你可以自己完成这个操作。只需在查询的最后添加explain()即可。
例如:
db.photos.find().limit(100).explain()


{
  "cursor" : "<Cursor Type and Index>",
  "isMultiKey" : <boolean>,
  "n" : <num>,
  "nscannedObjects" : <num>,
  "nscanned" : <num>,
  "nscannedObjectsAllPlans" : <num>,
  "nscannedAllPlans" : <num>,
  "scanAndOrder" : <boolean>,
  "indexOnly" : <boolean>,
  "nYields" : <num>,
  "nChunkSkips" : <num>,
  "millis" : <num>,
  "indexBounds" : { <index bounds> },
  "allPlans" : [
                 { "cursor" : "<Cursor Type and Index>",
                   "n" : <num>,
                   "nscannedObjects" : <num>,
                   "nscanned" : <num>,
                   "indexBounds" : { <index bounds> }
                 },
                  ...
               ],
  "oldPlan" : {
                "cursor" : "<Cursor Type and Index>",
                "indexBounds" : { <index bounds> }
              }
  "server" : "<host:port>",
}

mills param是您想要的

如果您想查看CPU使用情况,请在启动 mongod 脚本时添加--cpu 键。

--cpu
Forces mongod to report the percentage of CPU time in write lock. mongod generates output every four seconds. MongoDB writes this data to standard output or the logfile if using the logpath option.

http://docs.mongodb.org/manual/reference/explain/

对于projection(),您可以向mongo提供hint(),方法如下:

我们有一个简单的集合:

> db.performance.findOne()
{
        "_id" : ObjectId("50d2e4c08861fdb7e1c601ea"),
        "a" : 1,
        "b" : 1,
        "c" : 1,
        "d" : 1
}

这包括23个元素:

> db.performance.count()
23

现在我们可以创建复合索引:
> db.performance.ensureIndex({'c':1, 'd':1})

并提供Mongo使用索引以进行投影的提示。

> db.performance.find({'a':1}, {'c':1, 'd':1}).hint({'c':1, 'd':1}).explain()
{
        "cursor" : "BtreeCursor c_1_d_1",
        "isMultiKey" : false,
        "n" : 1,
        "nscannedObjects" : 23,
        "nscanned" : 23,
        "nscannedObjectsAllPlans" : 23,
        "nscannedAllPlans" : 23,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 0,
        "indexBounds" : {
                "c" : [
                        [
                                {
                                        "$minElement" : 1
                                },
                                {
                                        "$maxElement" : 1
                                }
                        ]
                ],
                "d" : [
                        [
                                {
                                        "$minElement" : 1
                                },
                                {
                                        "$maxElement" : 1
                                }
                        ]
                ]
        },
        "server" : ""
}
>

很好,但是CPU和内存利用率怎么样?有没有办法看到它们的变化? - Alexey Kamenskiy
这取决于许多因素。你的集合中有什么样的索引? - Dmitry Zagorulkin
查询没有条件,因为这里索引无关紧要。 - Sammaye
如果SO有复合索引{views:0,upvotes:0,downvotes:0},Mongo可以用它进行投影。 - Dmitry Zagorulkin
你有官方来源来证明吗?我的意思是索引对于投影有多有用?索引只适用于查找文档,而不是投影。在这里使用索引会使投影非常耗费资源且低效。 - Sammaye
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接