【问题标题】:MongoDB: Combine Aggregation and FilterMongoDB:结合聚合和过滤
【发布时间】:2019-03-13 21:02:39
【问题描述】:

有关背景信息,请参阅以下帖子:MongoDB C# Driver - Return last modified rows only

在运行此代码近两年后,我们最近一直遇到性能问题,尽管我一直在说代码不是问题,但基础架构坚持认为这是因为我正在执行全表扫描。

问题是特定于环境的问题。我们的 QA 环境一直像梦一样运行,但 Dev 和 Prod 有时很慢,有时却很好——非常不稳定。他们有相同的数据和代码,但 Dev 和 Prod 有另一个也在数据库上运行的应用程序。

我的数据有一个 Id 和一个 _id(或 AuditId) - 我按 Id 对数据进行分组,然后返回该记录的最后一个 _id,它没有被删除。我们有多个相同 ID 的历史记录,我想退回最后一个(请参阅原始帖子)。

所以我有以下方法:

private static FilterDefinition<T> ForLastAuditIds<T>(IMongoCollection<T> collection) where T : Auditable, IMongoAuditable
    {
        var pipeline = new[] { new BsonDocument { { "$group", new BsonDocument { { "_id", "$Id" }, { "LastAuditId", new BsonDocument { { "$max", "$_id" } } } } } } };
        var lastAuditIds = collection.Aggregate<Audit>(pipeline).ToListAsync().Result.ToList().Select(_ => _.LastAuditId);

        var forLastAuditIds = Builders<T>.Filter.Where(_ => lastAuditIds.Contains(_.AuditId) && _.Status != "DELETE");

        return forLastAuditIds;
    }

这个方法由下面的方法调用,它接受一个表达式,它附加到由 ForLastAuditIds 创建的 FilterDefinition。

protected List<T> GetLatest<T>(IMongoCollection<T> collection,
                                     Expression<Func<T, bool>> filter, ProjectionDefinition<T, T> projection = null,
                                     bool disableRoleCheck = false) where T : Auditable, IMongoAuditable
    {
        var forLastAuditIds = ForLastAuditIds(collection);

        var limitedList = (
                projection != null
                    ? collection.Find(forLastAuditIds & filter, new FindOptions()).Project(projection)
                    : collection.Find(forLastAuditIds & filter, new FindOptions())
            ).ToListAsync().Result.ToList();

        return limitedList;
    }

现在,所有这些工作都非常好,并且被我所有调用 Collections 的代码重复使用,但是这个特定的集合比其他集合要大得多,而且我们正在减慢那个集合。

我的问题是:有没有办法让我获取聚合和过滤器生成器并将它们组合起来以返回一个过滤器定义,我可以在不先运行全表扫描的情况下使用它?

我真的希望我说得通。

【问题讨论】:

标签: c# mongodb aggregation


【解决方案1】:

假设我完全理解你想要什么,这应该像这样简单:

首先,在LastAuditId 字段上放置一个降序索引:

db.collection.createIndex{ "LastAuditId": -1 /* for sorting */ }

或者甚至扩展索引以覆盖过滤器中的其他字段:

db.collection.createIndex{ "Status": 1, "LastAuditId": -1 /* for sorting */ }

不过,请确保您了解how indexes can/cannot support certain queries。并且始终使用explain() 来查看实际情况。

下一步要认识到,您必须始终尽可能多地进行筛选,以减少所需的排序数量。

所以,如果您需要,例如按Name 过滤,然后如果您的业务要求允许,请务必将其作为第一步。但是请注意,开始时的过滤会改变您的语义,因为您将获得每个通过前一个$match 阶段的Id 的最后修改文档,而不是每个发生的Id 的最后一个文档还要通过以下$match 阶段。

无论如何,最重要的是,一旦你得到一个排序集,你可以通过使用$group$first 轻松快速地获取最新的完整文档 - 使用正确的索引 - 不会进行集合不再扫描(现在将是索引扫描,因此速度更快)。

最后,您想通过 C# 利用 $$ROOT 变量运行与以下 MongoDB 查询等效的查询,以避免第二次查询(一旦您发布了 Audit、@ 987654336@ 和 IMongoAuditable 类型以及任何潜在的序列化程序/约定):

db.getCollection('collection').aggregate({
    $match: {
        /* some criteria that you currently get in the "Expression<Func<BsonDocument, bool>> filter" */
    }
}, {
    $sort: {
        "ModifiedDate": -1 // this will use the index!
    }
}, {
    $group: {
        "_id": "$Id",
        "document": { $first: "$$ROOT" } // no need to do a separate subsequent query or a $max/$min across the entire group because we're sorted!
    }
}, {
    $match: { // some additional filtering depending on your needs
        "document.Status": { $ne: "Delete" }
    }
})

最后,请注意,迁移到最新版本的 MongoDB 可能是个好主意,因为他们目前正在努力优化像您这样的聚合案例,例如这个:https://jira.mongodb.org/browse/SERVER-9507

【讨论】:

  • 非常感谢您的精彩回复,dnickless(那个昵称让我笑了!) 1. 我确实已经有一些关于收藏的索引,我会看看链接并确保他们设置正确。 2. 先做匹配的问题是,这些值可以在不同的审计之间改变,所以它必须在记录的最后一个版本上完成。 3.我尝试了$$root的值,但是在我的声明中,它没有回来,因为我认为集合项目太大了。在提供更多反馈之前,请接受您的建议并与它合作,再次感谢!
猜你喜欢
  • 2021-07-26
  • 1970-01-01
  • 2020-03-29
  • 2011-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-19
  • 1970-01-01
相关资源
最近更新 更多