MongoDB查询注释以及用户信息

时间:2015-09-05 04:54:33

标签: node.js mongodb

我正在使用nodejs和mongod(非mongoose)创建一个应用程序。我有一个让我头疼几天的问题,有人请为此提出建议!! 我有像这样的mongodb设计

post{
  _id:ObjectId(...),
  picture: 'some_url',
  comments:[
    {_id:ObjectId(...),
     user_id:Object('123456'),
     body:"some content"
    },
    {_id:ObjectId(...),
     user_id:Object('...'),
     body:"other content"
    } 
  ]
}

user{
 _id:ObjectId('123456'),
 name: 'some name', --> changable at any times
 username: 'some_name', --> changable at any times
 picture: 'url_link' --> changable at any times
}

我想查询帖子以及所有用户信息,以便查询如下所示:

[{
  _id:ObjectId(...),
  picture: 'some_url',
  comments:[
    {_id:ObjectId(...),
     user_id:Object('123456'),
     user_data:{
         _id:ObjectId('123456'),
         name: 'some name',
         username: 'some_name',
         picture: 'url_link'
     }
     body:"some content"
    },
    {_id:ObjectId(...),
     user_id:Object('...'),
     body:"other content"
    } 
  ]
}]

我尝试使用循环手动获取用户数据并添加评论,但事实证明这很困难,而且我的编码技能无法实现:(

请有人提出任何建议,我将非常感激。

P / s我正在尝试另一种方法,我会将所有用户数据嵌入到评论中,并且每当用户更新其用户名,名称或图片时。他们也会在所有评论中更新它

2 个答案:

答案 0 :(得分:7)

问题

作为written before,过度嵌入有几个问题:

问题1:BSON大小限制

截至撰写本文时,BSON documents are limited to 16MB。如果达到该限制,MongoDB将抛出异常并且您根本无法添加更多注释,并且在最坏的情况下,如果更改会增加文档的大小,甚至不会更改(用户)名称或图片。

问题2:查询限制和性能

在某些条件下查询或排序注释数组是不容易的。有些事情需要相当昂贵的汇总,有些则需要相当复杂的陈述。

虽然人们可以争辩说,一旦查询到位,这不是一个很大的问题,我不敢苟同。首先,对于开发人员和随后的MongoDBs查询优化器来说,查询越复杂,优化就越困难。通过简化数据模型和查询,我获得了最好的结果,在一个实例中将响应速度提高了100倍。

缩放时,与简单的数据模型和查询相比,复杂和/或代价高昂的查询所需的资源甚至可能会归结为整个机器。

问题3:可维护性

最后但并非最不重要的是,您可能会遇到维护代码的问题。作为一个简单的经验法则

  

代码越复杂,维护起来就越困难。更难的代码是维护,维护代码所需的时间越多。维护代码所需的时间越多,获得的代码就越贵。

     

结论:复杂的代码很昂贵。

在这种情况下,"昂贵"两者都指货币(专业项目)和时间(专业项目)。

(我的!)解决方案

非常简单:简化数据模型。因此,您的查询将变得不那么复杂,并且(希望)更快。

第1步:确定您的用例

这对我来说是一个疯狂的猜测,但重要的是向你展示一般方法。我按如下方式定义您的用例:

  1. 对于给定的帖子,用户应该能够发表评论
  2. 对于给定的帖子,请显示作者和评论,以及评论者和作者的用户名及其图片
  3. 对于给定的用户,应该可以轻松更改名称,用户名和图片
  4. 第2步:相应地建模数据

    用户

    首先,我们有一个简单的用户模型

    {
      _id: new ObjectId(),
      name: "Joe Average",
      username: "HotGrrrl96",
      picture: "some_link"
    }
    

    此处没有任何新内容,只是为了完整性而添加。

    帖子

    {
      _id: new ObjectId()
      title: "A post",
      content: " Interesting stuff",
      picture: "some_link",
      created: new ISODate(),
      author: {
        username: "HotGrrrl96",
        picture: "some_link"
      }
    }
    

    关于帖子的问题。这里有两点需要注意:首先,我们存储我们在显示帖子时立即需要的作者数据,因为这样可以节省我们对一个非常常见的(如果不是普遍存在的)用例的查询。为什么我们不保留评论和评论者的数据呢?由于16 MB size limit,我们试图阻止在单个文档中存储引用。相反,我们将引用存储在注释文档中:

    评论

    {
      _id: new ObjectId(),
      post: someObjectId,
      created: new ISODate(),
      commenter: {
        username: "FooBar",
        picture: "some_link"
      },
      comment: "Awesome!"
    }
    

    与帖子相同,我们拥有显示帖子的所有必要数据。

    查询

    我们现在取得的成就是我们规避了BSON大小限制,我们不需要引用用户数据以便能够显示帖子和评论,这可以为我们节省大量的查询。但是,让我们回到用例和更多查询

    添加评论

    现在这很简单。

    获取给定帖子的全部或部分评论

    对于所有评论

    db.comments.find({post:objectIdOfPost})
    

    对于最新的3条评论

    db.comments.find({post:objectIdOfPost}).sort({created:-1}).limit(3)
    

    因此,为了显示帖子及其所有(或部分)评论,包括用户名和图片,我们有两个查询。比以前更需要,但我们规避了大小限制,基本上你可以为每个帖子提供无限数量的评论。但是,让我们做一些真实的事情

    获取最新的5篇帖子及其最新的3条评论

    这是一个两步过程。但是,通过适当的索引(将在稍后回归),这仍然应该是快速的(因此节省资源):

    var posts = db.posts.find().sort({created:-1}).limit(5)
    posts.forEach(
      function(post) {
        doSomethingWith(post);
        var comments = db.comments.find({"post":post._id}).sort("created":-1).limit(3);
        doSomethingElseWith(comments);
      }
    )
    

    获取从最新到最旧的给定用户的所有帖子及其评论

    var posts = db.posts.find({"author.username": "HotGrrrl96"},{_id:1}).sort({"created":-1});
    var postIds = [];
    posts.forEach(
      function(post){
        postIds.push(post._id);
      }
    )
    var comments = db.comments.find({post: {$in: postIds}}).sort({post:1, created:-1});
    

    请注意,我们这里只有两个查询。虽然你需要手动"在帖子和他们各自的评论之间建立联系,这应该非常简单。

    更改用户名

    这可能是一个罕见的用例。但是,所述数据模型并不复杂

    首先,我们更改用户文档

    db.users.update(
      { username: "HotGrrrl96"},
      {
        $set: { username: "Joe Cool"},
        $push: {oldUsernames: "HotGrrrl96" }
      },
      {
        writeConcern: {w: "majority"}
      }
    );
    

    我们将旧用户名推送到相应的数组。这是一种安全措施,以防以下操作出现问题。此外,我们将写入关注设置为相当高的级别,以确保数据持久。

    db.posts.update(
      { "author.username": "HotGrrrl96"},
      { $set:{ "author.username": "Joe Cool"} },
      {
        multi:true,
        writeConcern: {w:"majority"}
      }
    )
    

    这里没什么特别的。注释的更新语句看起来几乎相同。虽然这些查询需要一些时间,但很少执行。

    指数

    根据经验,可以说MongoDB每个查询只能使用一个索引。虽然这不完全正确,因为有索引交叉点,很容易处理。另一件事是复合索引中的各个字段可以独立使用。因此,索引优化的一种简单方法是查找具有使用索引的操作中使用的大多数字段的查询,并创建它们的复合索引。请注意,查询中出现的顺序很重要。所以,让我们继续吧。

    帖子

    db.posts.createIndex({"author.username":1,"created":-1})
    

    评论

    db.comments.createIndex({"post":1, "created":-1})
    

    结论

    每个帖子的完全嵌入式文档无疑是加载它的最快方式及其评论。但是,它不能很好地扩展,并且由于处理它所需的复杂查询的性质,可以利用甚至消除这种性能优势。

    通过上述解决方案,您可以将一些速度(如果!)与基本无限的可扩展性以及更直接的数据处理方式进行交易。

    H个。

答案 1 :(得分:0)

您正在遵循标准化数据模型方法。如果您遵循此模型意味着,您必须编写另一个查询以获取用户信息,或者如果您使用嵌入式文档存储,那么每当用户文档更新时,所有用户文档都必须更改。 http://docs.mongodb.org/v3.0/reference/database-references/请阅读此链接以获取更多信息。