第一个匹配步骤后MongoDB聚合管道变慢

Jus*_*tin 2 mongodb mongodb-query aggregation-framework

我有一个 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" }
   ] 
})
Run Code Online (Sandbox Code Playgroud)

以下是示例 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
            }
Run Code Online (Sandbox Code Playgroud)

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

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

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

Vij*_*hit 6

2019年答案

此答案适用于 MongoDB 4.2

在阅读了问题和你们之间的讨论后,我相信问题已经解决,但对于所有使用 MongoDB 的人来说,优化仍然是一个常见问题。

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

如果我错了纠正我 :)

1.在集合上添加索引

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

您可以根据需要创建不同类型的索引。在此处了解有关索引的更多信息,官方 MongoDB 文档。

2. 流水线优化

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

    {
    $lookup: {
        from: "Collection",
        as: "resultingArrays",
        localField: "x",
        foreignField: "y",
        unwinding: { preserveNullAndEmptyArrays: false }
    
    Run Code Online (Sandbox Code Playgroud)

    } }

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

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

3. 重建索引

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

4. 删除不需要的索引

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

5. 限制文件

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

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

希望这篇总结能帮到你们,如果我错过了什么,请随时提出新的观点。我也会添加它们。