$sort 使我在 MongoDB 中的查询速度太慢

Moh*_*ian 2 mongodb mongodb-query aggregation-framework

我有一个这样的查询,我想按日期对我的结果进行排序。我在 DateTime 上有一个降序索引,在 UserId 上有一个升序索引,但是当我尝试按 DateTime 对我的结果进行排序时,它变得太慢了。

db.Users.aggregate([  
  { "$match" : { "UserId" : { "$in" : [NUUID("1b029f8b-a17e-3172-9247- 
                  9cddfaf9702b")] } } },       
  { "$match" : { "DateTime" : { "$gte" : ISODate("2018-08-15T12:54:38Z"), 
    "$lte" : ISODate("2018-08-25T12:54:38Z") } } },   
  { "$sort" : { "DateTime" : -1} }, { "$skip" : 0 }, { "$limit" : 20 }])
Run Code Online (Sandbox Code Playgroud)

当我删除排序部分时,它变得太快了。我尝试如下,它也太快了。

db.Users.aggregate([             
  { "$match" : { "DateTime" : { "$gte" : ISODate("2018-08-15T12:54:38Z"), 
    "$lte" : ISODate("2018-08-25T12:54:38Z") } } }, 
  { "$match" : { "UserId" : { "$in" : [NUUID("1b029f8b-a17e-3172-9247- 
     9cddfaf9702b")] } } },  
  { "$sort" : { "UserId" : 1} },{ "$skip" : 0 }, { "$limit" : 20 }])
Run Code Online (Sandbox Code Playgroud)

为什么只有当我想按日期时间排序时它才慢?这是我文档的结构

{
    "_id" : NUUID("11111111-1111-1111-1111-629f7992f895"),
    "DateTime" : ISODate("2018-08-23T15:49:51.153Z"),
    "UserId" : NUUID("aaaaaaaa-aaaa-aaaa-9247-9cddfaf9702b"),
    "PostId" : NUUID("bbbbbbbb-bbbb-bbbb-9529-d49ae48b2604"),
    "Type" : 3
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*nie 5

第一个查询的性能问题是您在DateTime(降序)和UserId(升序)上创建了单独的索引。当排序操作与谓词完全分离时,MongoDB(从 4.0 开始)无法使用索引交集对查询结果进行排序,因此如果这些是唯一可用的候选索引,则只能选择一个。

注意:尽管$match源管道中有两个阶段,但 MongoDB 服务器会将它们合并为一个$match阶段,这与使用$and.

为什么只有当我想按日期时间排序时它才慢?

在内存中对结果进行排序被认为是一项昂贵的操作,并且存在聚合阶段内存限制 (100MB),除非您还将该allowDiskUse选项添加到聚合中,否则不能超过该限制。在 MongoDB 4.0 中,查询计划器没有关于索引基数的统计信息,因此聚合将有利于支持高效排序的索引计划(就DateTime您而言)。您的第一个查询的结果将是索引扫描,以查找所有匹配DateTime值(按排序顺序)以及与具有UserId条件的每个匹配文档进行比较。

在 sorted by 的第二个查询中UserIdUserId索引可用于匹配和排序结果。结果仍然需要过滤DateTime,但UserId标准可能更具选择性,因此要扫描的文档更少。

支持这两种查询的理想索引应该是包含两者DateTimeUserId支持所需排序顺序的复合索引。例如:db.Users.createIndex({ UserId: 1, DateTime: -1})。如果添加此复合索引,您还可以删除原始{ UserId:1}索引,因为复合索引前缀可以有效地回答相同的查询。

了解查询性能的最直接方法是explain使用executionStats. 对于聚合管道,这种级别的解释细节需要 MongoDB 3.6+;对于较旧的服务器版本,您可以解释等效的find()查询。您的聚合查询当前不包括无法在标准find()查询中表达的任何处理阶段。

有关更多信息,请参阅MongoDB 文档中的使用索引对查询结果进行排序。博客文章Optimizing MongoDB Compound Indexes也有一些有用的背景(尽管使用了来自旧版本 MongoDB 的解释输出)。