应该针对不同的排序和过滤条件创建哪些MongoDB索引来提高性能?

Posted

技术标签:

【中文标题】应该针对不同的排序和过滤条件创建哪些MongoDB索引来提高性能?【英文标题】:Which MongoDB indexes should be created for different sorting and filtering conditions to improve performance? 【发布时间】:2019-12-19 12:43:18 【问题描述】:

我有大约 100,000,000 条记录的 MongoDB 集合。

在网站上,用户使用“细化搜索”功能搜索这些记录,他们可以在其中按多个条件进行过滤:

按国家、州、地区; 按价格范围; 按行业;

此外,他们还可以查看已排序的搜索结果:

按标题(asc/desc), 按价格(升序/降序), 按 bestMatch 字段。

我需要创建索引以避免对上述任何组合进行全面扫描(因为用户使用大多数组合)。跟着Equality-Sort-Range rule创建索引,我要创建很多索引:

所有过滤器组合 × 所有排序 × 所有范围过滤器,如下所示:

country_title
state_title
region_title
title_price
industry_title
country_title_price
country_industry_title
state_industry_title
...
country_price
state_price
region_price
...
country_bestMatch
state_bestMatch
region_bestMatch
...

实际上,我有更多的标准(包括相等和范围)和更多的排序。例如,我有多个价格字段,用户可以按任何价格排序,因此我必须为每个价格字段创建所有过滤索引,以防用户按该价格排序。

我们使用 MongoDB 4.0.9,目前只有一台服务器。

在我进行排序之前,它更容易,至少我可以拥有一个像 country_state_region 这样的复合索引,并且在搜索区域时始终在查询中包含国家和州。但是最后有排序字段,我不能再这样做了——我必须为位置(国家/州/地区)创建所有不同的索引以及所有排序组合。

另外,并非所有产品都有价格,所以我不能只按price 字段排序。相反,我必须创建两个索引:hasPrice: -1, price: 1hasPrice: -1, price: -1(这里,hasPrice 为 -1,无论价格排序方向如何,hasPrice=true 的记录总是首先出现)。

目前,我使用 NodeJS 代码生成类似于以下的索引(这是简化示例):

for (const filterFields of getAllCombinationsOf(['country', 'state', 'region', 'industry', 'price'])) 
    for (const sortingField of ['name', 'price', 'bestMatch']) 
        const index = 
            ...(_.fromPairs(filterFields.map(x => [x, 1]))),
            [sortingField]: 1
        ;
        await collection.ensureIndex(index);
    

因此,上面的代码生成了 90 多个索引。而在我的实际任务中,这个数字甚至更多。

是否有可能在不降低查询性能的情况下减少索引数量?

谢谢!

【问题讨论】:

【参考方案1】:

首先,在 MongoDB(参考:https://docs.mongodb.com/manual/reference/limits/)中,单个集合不能超过 64 个索引。此外,您永远不应该创建 64 个索引,除非没有写入或非常少。

是否有可能在不降低查询性能的情况下减少索引数量? 在不牺牲功能和查询性能的情况下,您不能这样做。

您可以做的几件事:(假设您使用分页来显示结果)

    在每一列上创建一个单独的(非复合)索引,并让 MongoDB 执行计划程序根据它拥有的元信息(基数、数字等)选择索引。当然,性能会受到影响。

    根据您的判断和一些分析,仅为最常用的组合创建复合索引。

    最重要的 - 创建复合索引时,您可以放弃排序列。假设您正在根据行业进行过滤并根据价格进行排序。如果你有一个复合指数(行业、价格),那么一切都会好起来的。但是,如果您只有行业的索引(假设分页结果),那么对于前几页的查询将非常快,但随着您进入下一页,查询会继续下降。通常,用户在浏览 5-6 页后不会进行导航。此外,您必须记住较大的跳过值,由于排序的 32mb 内存限制,查询将开始失败。这可以通过启用 allowDiskUse 的聚合(而不是查询)来克服。

    如果可以在您的用例中使用,请检查键集分页(也称为搜索方法)。

【讨论】:

感谢您的回答!我按照您所写的方式做了所有事情(包括第 3 点),但即使在第一页上,它的运行速度也很慢(如果我的索引仅基于过滤字段,而没有排序字段)。而且我总是使用聚合(allowDiskUse: true,只是为了这种情况),而不是 .find() 查询。所有过滤字段都存在于索引中。那么,如何实现快速性能呢?我是否应该创建具有相同内容和不同索引的多个集合才能执行不同的排序?这对我来说听起来很可怕,我无法相信这是“真正”的解决方案,至少对于 MongoDB 而言

以上是关于应该针对不同的排序和过滤条件创建哪些MongoDB索引来提高性能?的主要内容,如果未能解决你的问题,请参考以下文章

针对不同过滤条件的开闭原理

Mongodb创建用户角色

MongoDB和部分索引:在空日期过滤时避免过滤阶段

SQL 之 ON 和 WHERE执行顺序

MongoDB 条件查询和排序

您如何处理排序、分页和过滤的参数?