首页 > 解决方案 > 大集合中的 MongoDB 查询对

问题描述

我最近才开始使用 MongoDB 尝试解决特定于域的问题,但在尝试自我加入大集合时遇到了困难。我有一个包含超过 1000 万个文档的数据库,每个文档都包含一个实体的地址元素(个人、组织、组织的邮箱等)。请注意,每个深度(例如街道)可以多次出现以存储一些不同的信息,例如别名或特定于深度的 id。我没有架构限制,如果有助于解决问题,我可以更改它。

数据如下所示:

{
  "some_info": "xyz",
  "tags": {
    "HOUSE_NUMBER": [
      {
        "id": "23.45678",
        "value": "18",
        "attributes": ["NU"]
      }
    ],
    "FORENAME": [
      {
        "id": "34.56789",
        "value": "MAX",
        "attributes": ["XQ4", "M"]
      },
      {
        "id": "45.67890",
        "value": "X65732862",
        "attributes": ["XID"]
      }
    ],
    "STREET": [
      {
        "id": "56.789012",
        "value": "RICHMOND STREET",
        "attributes": []
      }
    ],
    "...": []
  }
}

我想查询集合中的对,例如“查找居住在同一条街道上的所有具有相同名字的人对”,或“查找所有居住在同一城市中至少有 3 个单词的人和组织对”。我当前对第一个问题的查询如下所示:

db.collection_name.aggregate([
    {$unwind: "$tags.STREET"},
    {$unwind: "$tags.FORENAME"},
    {
        $match: {
            "tags.FORENAME.attributes": {$nin: ["XID", "NA"]}
        }
    },
    {
        $lookup: {
            from: "collection_name",
            localField: "tags.STREET.id",
            foreignField: "tags.STREET.id",
            as: "joined"
        }
    },
    {$unwind: "$joined"},
    {$unwind: "$joined.tags.FORENAME"},
    {
        $match: {$expr: {$ne: ["$tags.FORENAME.id", "$joined.tags.FORENAME.id"]}}
    },
    {
        $match: {$expr: {$eq: ["$tags.FORENAME.value", "$joined.tags.FORENAME.value"]}}
    }
], {
    allowDiskUse: true
})

我在 tags.STREET.id、tags.FORENAME.id、tags.FORENAME.attributes 和 tags.FORENAME.value 上创建了索引。

问题在于执行时间。我只是无法达到可接受的水平,上述查询需要 3.5 分钟才能在我的机器上获得 500 个结果。相比之下,我的 PostgreSQL 数据库(针对问题专门创建的视图和索引)只需要几秒钟。

我怎样才能加快这种查询?MongoDB甚至适合这种问题吗?

标签: mongodb

解决方案


$lookup是 Mongo 执行的一个非常昂贵的阶段,在这种特定情况下,完全不需要。更不用说你在整个系列上都这样做了。

我会像这样重写这个管道,使用$group而不是$lookup

db.collection_name.aggregate([
        {
            $unwind: "$tags.STREET"
        },
        {
            $unwind: "$tags.FORENAME"
        },
        {
            $match: {
                "tags.FORENAME.attributes": {$nin: ["XID", "NA"]}
            }
        },
        {
            $group: {
                "_id": {foreName: "$tags.FORENAME.value", streetId: "tags.STREET.id"},
                docs: {$addToSet: "$$ROOT"}
            }
        },
        {
            $match: {
                "docs.1": {$exists: true}
            }
        },
        //Add whichever other structure changes you need.
    ],
    {
        allowDiskUse: true
});

现在这仍然是一个有点昂贵的管道,因为我们必须$unwind$group整个集合,但它会比当前版本快得多。

我不知道您的数据/产品是如何工作的,因此很难为应该如何构建模式/集合架构提供更好的“解决方案”。

话虽如此,我立即看到一个简单的改进是将streetandforename结构从数组更改为对象。(除非您可以拥有多个街道和姓名,然后关系数据库是否适合您?)这将使当前管道的前 3 个阶段变得多余,并提高性能。


编辑:分组时不可能否定条件,但我们可以通过添加一个额外的$group阶段来解决它。

db.collection_name.aggregate([
        {
            $unwind: "$tags.STREET"
        },
        {
            $unwind: "$tags.FORENAME"
        },
        {
            $unwind: "$tags.HOUSE_NUMBER"
        },
        {
            $match: {
                "tags.FORENAME.attributes": {$nin: ["XID", "NA"]}
            }
        },
        {
            $group:{
                "_id": {foreName: "$tags.FORENAME.value", streetId: "tags.STREET.id", houseName: "tags.HOUSE_NUMBER.id"},
                docs: {$addToSet: "$$ROOT"}
            }
        },
        {
            $group: {
                "_id": {foreName: "$tags.FORENAME.value", streetId: "tags.STREET.id"},
                docs: {$addToSet: "$docs"}
            }
        },
        {
            $match: {
                "docs.1": {$exists: true}
            }
        },
        //Add whichever other structure changes you need.
    ],
    {
        allowDiskUse: true
    });

推荐阅读