【问题标题】:How do I implement threaded comments?如何实现线程注释?
【发布时间】:2010-10-10 12:26:46
【问题描述】:

我正在开发一个可以支持线程化 cmets 的 Web 应用程序。我需要能够根据收到的票数重新排列 cmets。 (与 reddit 中线程化 cmets 的工作方式相同)

我很想听听 SO 社区关于如何做到这一点的意见。

我应该如何设计 cmets 表? 这是我现在使用的结构:

Comment
    id
    parent_post
    parent_comment
    author
    points

应该对这个结构做哪些改变?

我应该如何从该表中获取详细信息以便以正确的方式显示它们? (欢迎以任何语言实现。我只是想知道如何以最好的方式实现)

在实现此功能以减少 CPU/数据库负载时我需要注意哪些事项?

提前致谢。

【问题讨论】:

    标签: performance database-design comments query-optimization table-structure


    【解决方案1】:

    在数据库中存储树是一个有许多不同解决方案的主题。这取决于您是否还想检索子层次结构(因此项目 X 的​​所有子项),或者您是否只想获取整个层次结构集并使用字典以 O(n) 的方式在内存中构建树。

    您的表格的优势在于,您可以通过对 parentpost 进行过滤,一次性获取帖子中的所有 cmets。当您以教科书/幼稚的方式定义评论的父级时,您必须在内存中构建树(见下文)。如果要从数据库中获取树,则需要一种不同的方式来存储树: 在此处查看我对基于预计算的方法的描述: http://www.llblgen.com/tinyforum/GotoMessage.aspx?MessageID=17746&ThreadID=3208using balanced trees described by CELKO here:

    或另一种方法: http://www.sqlteam.com/article/more-trees-hierarchies-in-sql

    如果您在内存中获取层次结构中的所有内容并在那里构建树,由于查询非常简单,因此效率会更高:select .. from Comment where ParentPost = @id ORDER BY ParentComment ASC

    在查询之后,您在内存中构建树,只使用 1 个字典来跟踪元组 CommentID - Comment。您现在遍历结果集并动态构建树:遇到的每条评论,您都可以在字典中查找其父评论,然后将当前处理的评论也存储在该字典中。

    【讨论】:

    • “内存中”是指应用程序级别的吗?
    【解决方案2】:

    还要考虑一些事情...

    1) 当您根据排名或日期说“排序类似于 reddit”时,您是指顶级还是全部?

    2) 当你删除一个节点时,分支会发生什么?你重新养育他们吗?在我的实现中,我认为编辑器将决定 - 隐藏节点并将其与可见子项一起显示为“评论隐藏”,隐藏评论及其子项,或者核对整个树。重新养育应该很容易(只需将孩子的父母设置为被删除的父母),但是涉及整个树的任何事情似乎都很难在数据库中实现。

    我一直在研究 PostgreSQL 的 ltree 模块。它应该使涉及部分树的数据库操作更快一些。它基本上可以让您在表格中设置一个如下所示的字段:

    ltreetest=# select path from test where path <@ 'Top.Science';
                    path                
    ------------------------------------
     Top.Science
     Top.Science.Astronomy
     Top.Science.Astronomy.Astrophysics
     Top.Science.Astronomy.Cosmology
    

    但是,它本身并不能确保任何类型的参照完整性。换句话说,您可以拥有“Top.Science.Astronomy”的记录,而无需“Top.Science”或“Top”的记录。但它让你做的事情是:

    -- hide the children of Top.Science
    UPDATE test SET hide_me=true WHERE path @> 'Top.Science';
    

    -- nuke the cosmology branch
    DELETE FROM test WHERE path @> 'Top.Science.Cosmology';
    

    如果结合使用存储过程的传统“comment_id”/“parent_id”方法,我认为您可以两全其美。您可以使用您的“路径”快速遍历数据库中的评论树,并且仍然通过“comment_id”/“parent_id”确保引用完整性。我的设想是:

    CREATE TABLE comments (
    comment_id SERIAL PRIMARY KEY,
    parent_comment_id int REFERENCES comments(comment_id) ON UPDATE CASCADE ON DELETE CASCADE,
    thread_id int NOT NULL  REFERENCES threads(thread_id) ON UPDATE CASCADE ON DELETE CASCADE,
    path ltree NOT NULL,
    comment_body text NOT NULL,
    hide boolean not null default false
    );
    

    评论的路径字符串看起来像be

    <thread_id>.<parent_id_#1>.<parent_id_#2>.<parent_id_#3>.<my_comment_id>
    

    因此,comment_id 为“1”的线程“102”的根评论将具有以下路径:

    102.1

    comment_id 为“3”的孩子将是:

    102.1.3

    ID 为“31”和“54”的“3”的一些孩子将是:

    102.1.3.31
    102.1.3.54
    

    要隐藏节点“3”及其子节点,您可以发出以下命令:

    UPDATE comments SET hide=true WHERE path @> '102.1.3';
    

    我不知道——它可能会增加不必要的开销。另外,我不知道 ltree 的维护情况如何。

    【讨论】:

    • 我不认为删除节点是个好主意。如果“评论”被删除,则应删除内容或设置标志。所以渲染器知道如何响应。
    【解决方案3】:

    您当前的设计基本上适用于小型层次结构(少于千项)

    如果您想获取特定级别或深度,请将“级别”项目添加到您的结构中,并将其作为保存的一部分进行计算

    如果性能有问题,请使用合适的缓存

    【讨论】:

    • 我不懂级别?
    • @YasarArafath 他的意思是每个评论都应该知道它在树中的深度。例如,这允许您仅查询前 3 个级别。只有当用户展开评论树时,才会加载其余的 cmets
    • 三年前问过这个,谢谢回复
    【解决方案4】:

    我会在上面的表格中添加以下新字段:

    • thread_id:附加到特定对象的所有 cmets 的标识符

    • date:评论日期(允许按顺序获取cmets)

    • rank:评论排名(允许按排名获取评论顺序)

    使用这些字段,您将能够:

    1. 在单个操作中获取线程中的所有 cmets
    2. 按日期或排名在线程中排序 cmets

    不幸的是,如果您想将查询数据库保留为接近 SQL 标准,则必须在内存中重新创建树。一些数据库为分层数据提供特殊查询(例如 Oracle)

    ./亚历克斯

    【讨论】:

    • Alex,谢谢你的回答,但我不明白你的一些观点。我觉得thread_id和post_id一样,date可以换成自增的id,rank=points。这就是我在设计中所做的。你能澄清一下我的设计和提议的设计有什么不同吗?
    • @Niyaz:我想您可能需要编辑您的问题,因为我没有看到 post_id(实际上我误解了要点)。 thread_id:线程中所有 cmets 的唯一 id(附加到一段内容)。 autoincrement 可以提供顺序但不等同于日期(检查几乎所有论坛)。
    • 我很困惑。如果“parent_comment”指向父母的评论 id,我将其命名为“parent_comment_id”以清除空气。我不确定“parent_post”是什么意思,为什么它与“parent_comment”不同。
    • Parent_post 指的是原始帖子(不是任何 cmets)。我认为它可以让我通过单个查询而不是通过父子评论结构递归来获取与帖子相关的所有 cmets。
    • @Niyaz:据我所知,parent_post 相当于 thread_id。因此,看起来剩下的唯一要添加的内容是:日期、用户详细信息(如果需要)。
    猜你喜欢
    • 2018-07-18
    • 2021-11-16
    • 2012-09-06
    • 1970-01-01
    • 2018-04-08
    • 1970-01-01
    • 2011-08-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多