Nodejs mongoDB新手问多对多关系的文档设计

Nodejs mongoDB新手问多对多关系的文档设计

我看了cnodejs的源码,里面话题是topic,源码里面还有tag,对于一个话题,发布者可以设置多个tag。而一个tag里面也会有许多topic。这个是典型的多对多关系。 cndoejs的设计是这样的:有三个表,一个topic,一个tag,一个topic_tag。这个思路和关系型数据库是一样的。 我另外有一种办法。就是topic里面有一个tags数组,里面存放了tag的id,然后tag里面有一个topic数组,里面存放了topic的id。请问这样的设计好不好?会不会有什么问题?

7 回复

当然可以!让我们来详细探讨一下在MongoDB中如何处理多对多关系的设计,并且比较这两种不同的方法。

方法一:使用中间集合

这是CNodeJS采用的方法,通过引入一个新的集合来表示两个实体之间的关联。这种方法类似于关系型数据库中的中间表(junction table)。

示例代码:

Topic 集合

{
    "_id": ObjectId("5f3e4d2a7b8c9d0e1f2a3b4c"),
    "title": "Node.js 教程",
    "description": "学习 Node.js 的基础",
    "author": "John Doe",
    "tags": [
        ObjectId("5f3e4d2a7b8c9d0e1f2a3b4d"), // Tag ID
        ObjectId("5f3e4d2a7b8c9d0e1f2a3b4e")  // Another Tag ID
    ]
}

Tag 集合

{
    "_id": ObjectId("5f3e4d2a7b8c9d0e1f2a3b4d"),
    "name": "Node.js",
    "description": "关于 Node.js 的一切"
}

Topic_Tag 中间集合

{
    "_id": ObjectId("5f3e4d2a7b8c9d0e1f2a3b4f"),
    "topicId": ObjectId("5f3e4d2a7b8c9d0e1f2a3b4c"),
    "tagId": ObjectId("5f3e4d2a7b8c9d0e1f2a3b4d")
}

方法二:嵌入引用

这种方法是在TopicTag集合中分别存储对方的引用。

示例代码:

Topic 集合

{
    "_id": ObjectId("5f3e4d2a7b8c9d0e1f2a3b4c"),
    "title": "Node.js 教程",
    "description": "学习 Node.js 的基础",
    "author": "John Doe",
    "tags": [
        {
            "_id": ObjectId("5f3e4d2a7b8c9d0e1f2a3b4d"),
            "name": "Node.js",
            "description": "关于 Node.js 的一切"
        },
        {
            "_id": ObjectId("5f3e4d2a7b8c9d0e1f2a3b4e"),
            "name": "JavaScript",
            "description": "关于 JavaScript 的一切"
        }
    ]
}

Tag 集合

{
    "_id": ObjectId("5f3e4d2a7b8c9d0e1f2a3b4d"),
    "name": "Node.js",
    "description": "关于 Node.js 的一切",
    "topics": [
        {
            "_id": ObjectId("5f3e4d2a7b8c9d0e1f2a3b4c"),
            "title": "Node.js 教程",
            "description": "学习 Node.js 的基础",
            "author": "John Doe"
        }
    ]
}

比较与结论

优点

  • 中间集合:查询更高效,因为可以通过索引优化性能。
  • 嵌入引用:数据一致性更好,读取时不需要额外的查询。

缺点

  • 中间集合:需要维护更多的集合,增加了复杂性。
  • 嵌入引用:可能导致数据冗余,更新时需要小心处理。

根据你的具体需求,选择合适的方法。如果数据量较大,推荐使用中间集合;如果数据量较小且需要保持简单,可以选择嵌入引用的方式。


范式 和 反范式得 区别, 对于经常改动得东西,用范式, 不怎么改动,而且经常需要搜索得结果,一般用反范式,对于 一个tag 里面 存放多个tagid 是一个极度 不理智得行为,要想一下,你生成一个主题,需要遍历10篇文章里面得tagid,然后根据tagid找名字,菜都凉了,tag 一般都是一次生成,后面基本不怎么改动,如果需要搜索 也是 很简单得, tags: [tagName1, tagName2] db.collection.find({tags: tagName1}) mongodb 支持数组搜索。当然这样做也是有缺点得,tag作为数组,索引得空间会比较大,注意数组与数组之间做索引

感谢回复。我不是一个tag 里面 存放多个tagid。是这样的: topic: { tags: [tag_id1, tag_id2] }

tag: { topics: [topic_id1, topic_id2] }

等于是把topic_tag这个表合到两个表里面去了。而不是另外有一个表叫topic_tag

tag里面不应该存topic,如果想查找tag对应的topic列表可通过条件查找来topic实现

明白了,非常感谢!

这个给我先前遇到的问题差不多,当时所有的数据都放在了一个集合中,标签是一数组的形式保存,这样当一个页面需要两种数据的时候,调取数据就比较麻烦,后来请教人他说标签单独放个集合中。

在处理多对多关系时,常见的做法是在关系型数据库中使用第三个关联表来连接两个实体。而在MongoDB这种非关系型数据库中,处理多对多关系有多种方式。你提到的两种方法都有其优缺点,下面是针对这两种方法的分析:

方法一:使用关联表

这种方法类似于传统的关系型数据库设计。你提到的 topictagtopic_tag 表分别表示话题、标签和它们之间的关联。以下是简单的文档设计示例:

// topic 文档
{
    _id: ObjectId("6471e03a9577e83c524a9494"),
    title: "Node.js 入门",
    tags: [ObjectId("6471e03a9577e83c524a9495"), ObjectId("6471e03a9577e83c524a9496")]
}

// tag 文档
{
    _id: ObjectId("6471e03a9577e83c524a9495"),
    name: "Node.js"
}

// topic_tag 文档
{
    topicId: ObjectId("6471e03a9577e83c524a9494"),
    tagId: ObjectId("6471e03a9577e83c524a9495")
}

方法二:直接引用对方的ID

这种方法将所有标签ID直接存储在话题文档中,并且在每个标签文档中存储相关话题的ID。以下是示例:

// topic 文档
{
    _id: ObjectId("6471e03a9577e83c524a9494"),
    title: "Node.js 入门",
    tags: [ObjectId("6471e03a9577e83c524a9495"), ObjectId("6471e03a9577e83c524a9496")]
}

// tag 文档
{
    _id: ObjectId("6471e03a9577e83c524a9495"),
    name: "Node.js",
    topics: [ObjectId("6471e03a9577e83c524a9494")]
}

分析与建议

  • 关联表(方法一)的优点在于易于管理和查询。例如,你可以轻松地查询某个标签下的所有话题,或者某个话题下有哪些标签。缺点是需要额外维护一个关联表,可能会增加复杂度。

  • 直接引用(方法二)的优点在于简单,查询速度快。缺点是更新或删除标签/话题时需要确保所有相关的引用都正确更新,否则容易出现数据不一致。

总的来说,如果你的数据更新频率较低,推荐使用方法二;如果需要频繁操作或查询,建议使用方法一。实际应用中可以根据具体需求选择合适的方法。

回到顶部