MongoDB 数组查询性能

7

我正在尝试确定类似于约会应用程序的最佳模式。用户有一个列表(可能很多),他们可以查看其他用户的列表并对其进行“喜欢”和“不喜欢”。

目前,我只是在likedBydislikedBy数组中存储其他人的列表ID。当用户“喜欢”列表时,它会将其列表ID放入“喜欢”的列表数组中。但是,现在我想跟踪用户喜欢列表的时间戳。这将用于用户的“历史记录列表”或数据分析。

我需要执行两个单独的查询:

查找此用户以前未喜欢或不喜欢的所有活动列表

以及用户“喜欢”/“不喜欢”选择的历史记录

按时间顺序查找用户X喜欢的所有列表

我的当前模式是:

listings
  _id: 'sdf3f'
  likedBy: ['12ac', 'as3vd', 'sadf3']
  dislikedBy: ['asdf', 'sdsdf', 'asdfas']
  active: bool

我可以像这样做吗?
listings
  _id: 'sdf3f'
  likedBy: [{'12ac', date: Date}, {'ds3d', date: Date}]
  dislikedBy: [{'s12ac', date: Date}, {'6fs3d', date: Date}]
  active: bool

我也在考虑为choices创建一个新的集合。

choices
  Id
  userId          // id of current user making the choice
  userlistId      // listing of the user making the choice
  listingChoseId  // the listing they chose yes/no
  type
  date

我不确定在执行“查找此用户以前未喜欢或不喜欢的所有活动列表”时,将这些选项放入另一个集合中会产生什么性能影响。

如果您有任何见解,我们将不胜感激!


当你没有明确表达你打算如何使用时,这确实使得任何人都难以评论应该使用什么样的模式。假设你需要添加时间戳信息是有原因的,所以如果你在问题中分享了你想要使用它的原因,那么就有东西可以回答了。 - Neil Lunn
感谢@NeilLunn,我稍微改写了一下以使其更易于理解。基本上,我需要能够运行一个查询来查找所有未选择的列表,并运行另一个查询来获取用户的“喜欢”和“不喜欢”的历史记录。 - SkinnyGeek1010
1个回答

41

显然,您认为将这些嵌入到“列表”文档中是一个好主意,以便您的附加使用模式能够正确地适用于此处提供的案例。考虑到这一点,没有理由抛弃它。

不过,需要澄清的是,您似乎想要的结构类似于这样:

{
    "_id": "sdf3f",
    "likedBy": [
         { "userId": "12ac",  "date": ISODate("2014-04-09T07:30:47.091Z") },
         { "userId": "as3vd", "date": ISODate("2014-04-09T07:30:47.091Z") },
         { "userId": "sadf3", "date": ISODate("2014-04-09T07:30:47.091Z") }
    ],
    "dislikedBy": [
        { "userId": "asdf",   "date": ISODate("2014-04-09T07:30:47.091Z") },
        { "userId": "sdsdf",  "date": ISODate("2014-04-09T07:30:47.091Z") },
        { "userId": "asdfas", "date": ISODate("2014-04-09T07:30:47.091Z") }
    ],
    "active": true
}

除了有一个小问题,一切都很好。由于这个内容在两个数组字段中,您将无法在这两个字段上创建索引。这是一个限制,只能在复合索引中包含一个数组类型的字段(或多键)。

因此,为了解决第一个查询无法使用索引的明显问题,您应该像这样进行结构化:

{
    "_id": "sdf3f",
    "votes": [
        { 
            "userId": "12ac",
            "type": "like", 
            "date": ISODate("2014-04-09T07:30:47.091Z")
        },
        {
            "userId": "as3vd",
            "type": "like",
            "date": ISODate("2014-04-09T07:30:47.091Z")
        },
        { 
            "userId": "sadf3", 
            "type": "like", 
            "date": ISODate("2014-04-09T07:30:47.091Z")
        },
        { 
            "userId": "asdf", 
            "type": "dislike",
            "date": ISODate("2014-04-09T07:30:47.091Z")
        },
        {
            "userId": "sdsdf",
            "type": "dislike", 
            "date": ISODate("2014-04-09T07:30:47.091Z")
        },
        { 
            "userId": "asdfas", 
            "type": "dislike",
            "date": ISODate("2014-04-09T07:30:47.091Z")
        }
    ],
    "active": true
}

这允许创建一个涵盖此表单的索引:

db.post.ensureIndex({
    "active": 1,
    "votes.userId": 1, 
    "votes.date": 1, 
    "votes.type": 1 
})

实际上,你可能需要一些索引来适应你的使用模式,但关键是现在你可以使用索引。

针对第一个情况,你可以使用以下查询形式:

db.post.find({ "active": true, "votes.userId": { "$ne": "12ac" } })

考虑到每个用户显然不可能同时有喜欢和不喜欢两个选项,这一点很有道理。按照该索引的顺序,至少可以使用"active"来进行过滤,因为你的否定条件需要扫描其他所有内容。无论使用哪种结构,都无法避免这种情况。

对于另一种情况,你可能希望将用户ID置于日期之前,并作为第一个元素放入索引中。那么你的查询就非常简单了:

db.post.find({ "votes.userId": "12ac" })
    .sort({ "votes.userId": 1, "votes.date": 1 })

但是你可能会想知道,突然间你失去了一些东西,因为在以前,获取“赞”和“踩”的数量就像测试数组大小一样容易,但现在有些不同了。不过这并不是不能通过使用聚合函数解决的:

db.post.aggregate([
    { "$unwind": "$votes" },
    { "$group": {
       "_id": {
           "_id": "$_id",
           "active": "$active"
       },
       "likes": { "$sum": { "$cond": [
           { "$eq": [ "$votes.type", "like" ] },
           1,
           0
       ]}},
       "dislikes": { "$sum": { "$cond": [
           { "$eq": [ "$votes.type", "dislike" ] },
           1,
           0
       ]}}
])

所以,无论您实际使用的形式如何,都可以将文档中的任何重要部分存储在组合_id中,然后轻松地评估“喜欢”和“不喜欢”的数量。

您可能还没有注意到,将一个条目从“喜欢”更改为“不喜欢”也可以通过单个原子更新完成。

您可以做更多的事情,但出于给定原因,我更喜欢这种结构。


谢谢!这真的很有帮助! - SkinnyGeek1010
1
为什么这个回答没有得到更多的赞?它非常清晰明了。 - spartikus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接