【问题标题】:MongoDB aggregate pipeline slow after first match step第一个匹配步骤后 MongoDB 聚合管道变慢
【发布时间】:2019-08-21 16:24:36
【问题描述】:

我有一个 MongoDB 聚合管道,其中包含许多步骤(匹配索引字段、添加字段、排序、折叠、再次排序、页面、项目结果。)如果我注释掉除第一个匹配步骤之外的所有步骤,查询执行速度超快(0.075 秒),因为它利用了正确的索引。但是,如果我随后尝试执行任何后续步骤,即使是像获取结果计数这样简单的操作,查询也会开始花费 27 秒!!!

这是查询:(不要太纠结于它的复杂性,因为索引正在快速执行它......)

db.runCommand({ 
  aggregate: 'ResidentialProperty', 
  allowDiskUse: false, 
  explain: false,
  cursor: {}, 
  pipeline: 
    [
      {
                "$match" : {
                    "$and" : [ 
                        {
                            "CountyPlaceId" : 20006073
                        }, 
                        {
                            "$or" : [ 
                                {
                                    "$and" : [ 
                                        {
                                            "ForSaleGroupId" : {
                                                "$in" : [ 
                                                    2, 
                                                    3
                                                ]
                                            }
                                        }, 
                                        {
                                            "$or" : [ 
                                                {
                                                    "ForSaleGroupId" : {
                                                        "$nin" : [ 
                                                            2, 
                                                            3
                                                        ]
                                                    }
                                                }, 
                                                {
                                                    "ListDate" : {
                                                        "$gte" : ISODate("2019-02-21T00:00:00.000Z")
                                                    }
                                                }
                                            ]
                                        }, 
                                        {
                                            "$or" : [ 
                                                {
                                                    "ForSaleGroupId" : {
                                                        "$ne" : 3
                                                    }
                                                }, 
                                                {
                                                    "PendingSaleDate" : {
                                                        "$gte" : ISODate("2019-02-21T00:00:00.000Z")
                                                    }
                                                }
                                            ]
                                        }
                                    ]
                                }, 
                                {
                                    "ForLeaseGroupId" : {
                                        "$in" : [ 
                                            2, 
                                            3
                                        ]
                                    },
                                    "$or" : [ 
                                        {
                                            "ForLeaseGroupId" : {
                                                "$nin" : [ 
                                                    2, 
                                                    3
                                                ]
                                            }
                                        }, 
                                        {
                                            "ListDate" : {
                                                "$gte" : ISODate("2019-02-21T00:00:00.000Z")
                                            }
                                        }
                                    ]
                                }, 
                                {
                                    "DistressedGroupId" : {
                                        "$in" : [ 
                                            2, 
                                            3, 
                                            4
                                        ]
                                    },
                                    "$or" : [ 
                                        {
                                            "DistressedGroupId" : 1
                                        }, 
                                        {
                                            "DistressedDate" : {
                                                "$gte" : ISODate("2019-02-21T00:00:00.000Z")
                                            }
                                        }
                                    ]
                                }, 
                                {
                                    "$and" : [ 
                                        {
                                            "OffMarketGroupId" : {
                                                "$in" : [ 
                                                    3, 
                                                    8
                                                ]
                                            }
                                        }, 
                                        {
                                            "$or" : [ 
                                                {
                                                    "OffMarketGroupId" : 1
                                                }, 
                                                {
                                                    "OffMarketDate" : {
                                                        "$gte" : ISODate("2019-02-21T00:00:00.000Z")
                                                    }
                                                }
                                            ]
                                        }, 
                                        {
                                            "$or" : [ 
                                                {
                                                    "OffMarketGroupId" : {
                                                        "$nin" : [ 
                                                            7, 
                                                            8
                                                        ]
                                                    }
                                                }, 
                                                {
                                                    "SoldDate" : {
                                                        "$gte" : ISODate("2019-02-21T00:00:00.000Z")
                                                    }
                                                }, 
                                                {
                                                    "OffMarketDate" : {
                                                        "$gte" : ISODate("2019-02-21T00:00:00.000Z")
                                                    }
                                                }
                                            ]
                                        }
                                    ]
                                }, 
                                {
                                    "$or" : [ 
                                        {
                                            "ForSaleGroupId" : {
                                                "$ne" : 1
                                            }
                                        }, 
                                        {
                                            "OffMarketGroupId" : 6
                                        }
                                    ],
                                    "ChangedListPriceDate" : {
                                        "$gte" : ISODate("2019-02-21T00:00:00.000Z")
                                    }
                                }
                            ]
                        }, 
                        {
                            "$or" : [ 
                                {
                                    "ForSaleGroupId" : {
                                        "$ne" : 1
                                    }
                                }, 
                                {
                                    "ForLeaseGroupId" : {
                                        "$ne" : 1
                                    }
                                }, 
                                {
                                    "OffMarketGroupId" : 6
                                }, 
                                {
                                    "IsListingOnly" : true
                                }, 
                                {
                                    "OrgId" : ""
                                }, 
                                {
                                    "OffMarketDate" : {
                                        "$gte" : ISODate("2018-11-23T00:00:00.000Z")
                                    }
                                }
                            ]
                        }, 
                        {
                            "PropertyTypeId" : {
                                "$in" : [ 
                                    1, 
                                    5, 
                                    6
                                ]
                            }
                        }
                    ]
                }
            }, 
      // Other steps ommitted, since it's slow regardless...
      { "$count": "Count" }
   ] 
})

这是一个示例 ResidentialProperty 文档的样子:

{
                "_id" : 294401911,
                "PropertyId" : 86689647,
                "OrgId" : "caclaw-n",
                "OrgSecurableId" : 1,
                "ListingId" : "19443870",
                "Location" : {
                    "type" : "Point",
                    "coordinates" : [ 
                        -117.316207, 
                        33.104623
                    ]
                },
                "CountyPlaceId" : 20006073,
                "CityPlaceId" : 50611194,
                "ZipCodePlaceId" : 70092011,
                "MetropolitanAreaPlaceId" : 10041740,
                "MinorCivilDivisionPlaceId" : 30002074,
                "NeighborhoodPlaceId" : 150813707,
                "MacroNeighborhoodPlaceId" : 160051666,
                "SubNeighborhoodPlaceId" : null,
                "ResidentialNeighborhoodsPlaceId" : 220978234,
                "ForSaleGroupId" : 1,
                "DistressedGroupId" : 1,
                "OffMarketGroupId" : 1,
                "ForLeaseGroupId" : 2,
                "ForSaleDistressedGroupId" : 1,
                "OffMarketDistressedGroupId" : 1,
                "ListDate" : ISODate("2019-03-15T00:00:00.000Z"),
                "PendingSaleDate" : null,
                "OffMarketDate" : null,
                "DistressedDate" : null,
                "SoldDate" : null,
                "ChangedListPriceDate" : null,
                "ListPrice" : null,
                "ListPriceRangeLow" : null,
                "ListPriceRangeHigh" : null,
                "ListPricePerSqFt" : null,
                "ListPricePerLotSizeSqFt" : null,
                "SoldPrice" : 0,
                "SoldPricePerSqFt" : 0.0,
                "SoldPricePerLotSizeSqFt" : 0.0,
                "MonthlyLeaseListPrice" : 6950.0,
                "MonthlyLeaseListPricePerSqFt" : 2.5402,
                "MonthlyLeaseListPricePerLotSizeSqFt" : 2.5402,
                "MonthlyLeaseSoldPrice" : null,
                "MonthlyLeaseSoldPricePerSqFt" : null,
                "MonthlyLeaseSoldPricePerLotSizeSqFt" : null,
                "SoldToListPriceRatio" : 0.0,
                "EstimatedToListPriceRatio" : 0.0,
                "AppPropertyModeId" : 1,
                "PropertyTypeId" : 1,
                "PropertySubTypeId" : null,
                "Bedrooms" : 4,
                "Bathrooms" : 3,
                "LivingAreaInSqFt" : 2736,
                "LotSizeInSqFt" : NumberLong(5073),
                "YearBuilt" : 2004,
                "GarageSpaces" : 2,
                "BuildingSizeInSqFt" : 2736,
                "Units" : 1,
                "Rooms" : null,
                "NetIncome" : null,
                "EstimateTypeId" : 3,
                "EstimatedValue" : 1253740,
                "EstimatedValuePerSqFt" : 458.2383,
                "EstimatedValuePerLotSizeSqFt" : 247.1397,
                "CapRate" : null,
                "Keywords" : [ 
                    "$6,950/month long-term minimum of 30 days. $8,950 June and then $9,950 for July or August. BeautifulWaters End Luxury Home walking distance to the beach. Short or Long term Fully Furnished (1 Month plus) with brand new furnishings & fresh paint & new carpets. Enjoy the beach & golf community lifestyle of Carlsbad, CA in this delightful North County San Diego vacation rental home!  This spacious & comfortable two story single family home sits on a cul-de-sac in the gated community of Waters End. Easy walk to the beach and close proximity to the Carlsbad train station, area restaurants, shopping, golf courses, and San Diego theme park attractions. The community also offers many health and beauty spas, yoga, and meditation centers, nearby world-renowned golf courses (such as Torrey Pines, Aviara, and La Costa Resort and Spa) as well as some of the best cycling in all of San Diego County.", 
                    "San Diego (City) (Sd)", 
                    "R1", 
                    "Single Family"
                ],
                "OwnerName" : "Brookside Land Trust, ; State Trustee Services Llc",
                "TenantNames" : null,
                "Apn" : "214-610-49-00",
                "OpenHouseStartDate" : null,
                "OpenHouseEndDate" : null,
                "ListingPhotoCount" : 25,
                "StatusChangedDate" : ISODate("2019-06-28T00:00:00.000Z"),
                "SortAddress" : "BrooksideCtZZZZZZZZZZ00000000000000000617ZZZZZCarlsbadCA92011",
                "SortOwnerName" : "BrooksideLandTrust,;State",
                "ListingIdAlphaNum" : "19443870",
                "IsListingOnly" : false
            }

计数返回 27,815 个结果。我不认为这是一个索引问题,因为第一个匹配步骤执行得如此之快。我也不认为这是每个聚合管道步骤达到 100mb 内存限制的问题,因为我设置了 allowDiskUse: false ,但它仍在执行查询而不会出错。

同样有趣的是,在第一个匹配步骤之后,针对同一集合的另一个聚合管道查询过滤到 45,081 条记录,但是当我在之后执行计数时,它仅在 3 秒内返回。所以这个问题不能真正归咎于文档结构。

那么这里到底发生了什么?为什么匹配过滤如此之快,而之后的任何操作,即使是像计数这样简单的操作,都非常慢?我已经尝试启用 explain: true 并且我没有看到任何突出的东西。匹配操作表明它使用了正确的索引。计数操作在说明中不包含任何其他详细信息。

【问题讨论】:

  • 鉴于$or 的复杂性和大量使用,很难想象您的$match 是如何被索引很好地支持的。当您只是测试$match 时,您是用尽了结果游标还是仅仅获得了第一组结果?
  • 澄清上述评论:MongoDB 游标不会一次检索整个结果集。相反,在任何给定时间检索小批量以进行迭代。如果您尝试仅使用 $match 结果遍历游标,很有可能仍然会遇到 27 秒的执行时间。由于条件分支的数量和要查询的不同字段的数量,您的初始匹配不太可能有效。
  • 您的第一组更改应该是从顶级 $and 操作(CountyPlaceIdPropertyTypeId)中删除第一个和最后一个条目,并将它们放在最开始匹配作为您的查询谓词。然后,您应该拥有三个顶级$match 字段CountyPlaceIdPropertyTypeId$and。这将显着减少初始匹配中的开销。可能需要进一步优化,但请先进行这些更改并从那里开始。
  • 谢谢,你们俩都很准。我曾假设,由于第一步执行得很快,查询和索引没有问题,但我只检索前 50 个结果,所以这就是它如此之快的原因。我能够优化查询,现在获得了更合理的 2-3 秒持续时间。如果有人想发表他们的评论作为答案,我会接受。

标签: mongodb mongodb-query aggregation-framework


【解决方案1】:

2019 年回答

此答案适用于 MongoDB 4.2

看了大家的问题和讨论,我相信问题已经解决了,但优化仍然是所有使用MongoDB的人的普遍问题。

我遇到了同样的问题,这里是查询优化的技巧。

如果我错了,请纠正我:)

1.在集合上添加索引

索引在快速运行查询方面起着至关重要的作用,因为索引是一种数据结构,可以以易于遍历的形式存储集合的数据集。借助 MongoDB 中的索引可以有效地执行查询。

您可以根据需要创建不同类型的索引。详细了解索引here,MongoDB 官方文档。

2。流水线优化

  • 始终在 $project 之前使用 $match,因为过滤器会从下一阶段删除额外的文档和字段。
  • 永远记住,索引由 $match 和 $sort 使用。因此,请尝试为要对文档进行排序或过滤的字段添加索引。
  • 尝试在您的查询中保留此顺序,在 $limit 之前使用 $sort,例如 $sort + $limit + $skip。因为$sort利用了索引,允许MongoDB在执行查询的同时选择需要的查询计划。
  • 始终在 $skip 之前使用 $limit,以便将跳过应用于限制文档。
  • 使用 $project 仅返回下一阶段所需的数据。
  • 始终在 $lookup 中的 foreignField 属性上创建索引。此外,由于查找产生一个数组,我们通常在下一阶段展开它。因此,与其在下一阶段展开它,不如在查找中展开它:

    {
    $lookup: {
        from: "Collection",
        as: "resultingArrays",
        localField: "x",
        foreignField: "y",
        unwinding: { preserveNullAndEmptyArrays: false }
    

    } }

  • 在聚合中使用allowDiskUse,借助它聚合操作可以将数据写入Database Path目录下的_tmp子目录。它用于对临时目录执行大型查询。例如:

     db.orders.aggregate(
     [
            { $match: { status: "A" } },
            { $group: { _id: "$uid", total: { $sum: 1 } } },
            { $sort: { total: -1 } }
     ],
     {
            allowDiskUse: true
     },
     )
    

3.重建索引

如果您经常创建和删除索引,请重建您的索引。它可以帮助 MongoDB 刷新以前存储的查询计划,缓存,它会继续接管所需的查询计划,相信我,这个问题很糟糕:(

4.删除不需要的索引

太多的索引在创建、更新和删除操作中花费了太多时间,因为他们需要创建索引以及他们的任务。因此,删除它们会有很大帮助。

5.限制文档

在真实场景中,获取数据库中存在的完整数据无济于事。此外,要么您无法显示它,要么用户无法读取完整的获取数据。因此,不要获取完整的数据,而是以块的形式获取数据,这有助于您和您的客户查看该数据。

最后,观察 MongoDB 选择的执行计划有助于找出主要问题。 所以,$explain 将帮助您解决这个问题。

希望这个总结能对你们有所帮助,如果我错过了任何新观点,请随时提出新观点。我也会添加它们。

【讨论】:

  • 请停止在其他帖子下方的 cmets 中向该问题发送垃圾链接。
猜你喜欢
  • 1970-01-01
  • 2023-02-25
  • 1970-01-01
  • 2020-06-13
  • 1970-01-01
  • 1970-01-01
  • 2019-02-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多