存储树数据的快速关系方法(例如文章的线程评论)

Posted

技术标签:

【中文标题】存储树数据的快速关系方法(例如文章的线程评论)【英文标题】:Fast Relational method of storing tree data (for instance threaded comments on articles) 【发布时间】:2010-10-25 04:13:53 【问题描述】:

我有一个 cms,它根据文章存储 cmets。这些 cmets 既可以是螺纹的,也可以是非螺纹的。尽管从技术上讲,它们是相同的,只是在未线程化时将回复列留空。我的应用程序适用于 sqlLite、mysql 和 pgsql,所以我需要相当标准的 SQL。

我目前有一个评论表

comment_id
article_id
user_id
comment
timestamp
thread (this is the reply column)

我的问题是弄清楚如何最好地表示数据库中的线程化 cmets。也许在一个单独的表格中,它支持没有内容的树集和一个简单的表格来保存文本?也许它已经是这样了?也许是另一种方式?

如果 cmets 没有线程,我可以轻松地按时间戳排序。

如果它们是线程化的,我会这样排序

ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))

从 ORDER BY 中可以看出,注释查询永远不会使用索引作为基于函数的索引,仅真正存在于 Oracle 中。帮助我快速创建评论页面。

【问题讨论】:

“最佳”实现取决于数据结构和读/写特性。要获得近乎全面的社区更新选项列表,以考虑最适合您的选项:***.com/questions/4048151 【参考方案1】:

我知道答案有点晚了,但是对于树数据使用闭包表,这是正确的关系方式。 http://www.slideshare.net/billkarwin/models-for-hierarchical-data

它描述了4种方法:

Adjcency 列表(简单的父外键) 路径枚举(已接受答案中提到的 Drupal 策略) 嵌套集 闭包表(将祖先/后代事实存储在单独的关系 [表] 中,并带有可能的距离列)

与其他选项相比,最后一个选项具有简单的 CRUD 操作的优点。成本是空间,在最坏的情况下,在数字树节点中是 O(n^2) 大小,但在实践中可能并没有那么糟糕。

【讨论】:

非常酷!闭包表看起来很有希望。原始答案可能应该提供来自资源的实际信息,而不仅仅是指向资源的链接。我进行了编辑以包含关键要点。【参考方案2】:

您可以在邻接集模型和嵌套集模型之间进行选择。文章Managing Hierarchical Data in MySQL 做了一个很好的介绍。

有关理论讨论,请参阅 Celko 的 Trees and Hierarchies。

如果您的数据库支持窗口函数,则实现线程列表相当容易。您所需要的只是目标数据库表中的递归引用,例如:

create Tablename (
  RecordID integer not null default 0 auto_increment,
  ParentID integer default null references RecordID,
  ...
)

然后您可以使用递归公用表表达式来显示线程视图。 here 提供了一个示例。

【讨论】:

【参考方案3】:

实际上,我只是自己做的!我使用嵌套集模型来表示关系数据库中的分层数据。

Managing Hierarchical Data in MySQL 对我来说是纯金。嵌套集是该文章中描述的第二种模型。

【讨论】:

嵌套集的糟糕之处在于,以任何方式修改树结构都是昂贵的【参考方案4】:

我真的很喜欢Drupal 解决这个问题的方式。它为每个评论分配一个线程 ID。对于第一条评论,此 id 从 1 开始。如果对此评论添加回复,则会为其分配 ID 1.1。回复评论1.1 的线程ID 为1.1.1。注释 1.1 的兄弟姐妹被赋予线程 ID 1.2。你明白了。添加评论时,只需一次查询即可轻松计算这些线程 ID。

当线程被渲染时,属于该线程的所有 cmets 都在一个查询中被获取,并按线程 ID 排序。这会按升序为您提供线程。此外,使用线程ID,您可以找到每个评论的嵌套级别,并相应地缩进。

1
1.1
1.1.1
1.2
1.2.1

有几个问题需要解决:

如果线程 id 的一个组成部分增长到 2 位,则按线程 id 排序将不会产生预期的顺序。一个简单的解决方案是确保线程 ID 的所有组件都用零填充以具有相同的宽度。 按线程 ID 降序排序不会产生预期的降序。

Drupal 使用称为 vancode 的编号系统以更复杂的方式解决了第一个问题。至于第二个问题,它是通过在按降序排序时在线程ID后附加一个反斜杠(其ASCII码高于数字)来解决的。您可以通过查看comments module 的源代码找到有关此实现的更多详细信息(请参阅函数comment_get_thread 之前的大注释)。

【讨论】:

【参考方案5】:

不幸的是,纯 SQL 方法很慢。

@Marc W 提出的 NESTED SETS 非常优雅,但如果您的树枝达到范围,则可能需要更新整棵树,这可能会很慢。

请参阅我的博客中的这篇文章,了解如何在MySQL 中快速做到这一点:

Hierarchical queries in MySQL - 模拟 OracleCONNECT BY

您需要创建一个函数:

CREATE FUNCTION hierarchy_connect_by_parent_eq_prior_id(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
        DECLARE _id INT;
        DECLARE _parent INT;
        DECLARE _next INT;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET @id = NULL;

        SET _parent = @id;
        SET _id = -1;

        IF @id IS NULL THEN
                RETURN NULL;
        END IF;

        LOOP
                SELECT  MIN(id)
                INTO    @id
                FROM    t_hierarchy
                WHERE   parent = _parent
                        AND id > _id;
                IF @id IS NOT NULL OR _parent = @start_with THEN
                        SET @level = @level + 1;
                        RETURN @id;
                END IF;
                SET @level := @level - 1;
                SELECT  id, parent
                INTO    _id, _parent
                FROM    t_hierarchy
                WHERE   id = _parent;
        END LOOP;
END

并在这样的查询中使用它:

SELECT  hi.*
FROM    (
        SELECT  hierarchy_connect_by_parent_eq_prior_id(id) AS id, @level AS level
        FROM    (
                SELECT  @start_with := 0,
                        @id := @start_with,
                        @level := 0
                ) vars, t_hierarchy
        WHERE   @id IS NOT NULL
        ) ho
JOIN    t_hierarchy hi
ON      hi.id = ho.id

这当然是 MySQL 特定的,但它真的很快。

如果您希望它在 PostgreSQLMySQL 之间可移植,您可以使用 PostgreSQL 的 contrib for CONNECT BY 并将查询包装到两个系统同名的存储过程中。

【讨论】:

【参考方案6】:

其实还是要在读写之间取得平衡。

如果您可以在每次插入时更新一堆行,那么嵌套集(或等效的)将为您提供轻松、快速的读取。

除此之外,父级上的简单 FK 将为您提供超简单的插入,但很可能是检索的噩梦。

我想我会使用嵌套集,但要注意预期的数据量和使用模式(为每个插入更新两个索引列(用于左右信息)上的几行,也许很多行可能在某些时候成为问题)。

【讨论】:

以上是关于存储树数据的快速关系方法(例如文章的线程评论)的主要内容,如果未能解决你的问题,请参考以下文章

一种理想的在关系数据库中存储树型结构数据的方法

如何从存储在数据库中的信息中显示树?

面向对象存储框架:Obase快速入门

wordpress数据库结构以及数据表之间的关系

为不同供应商存储客户评论的正确关系数据库设计是啥?

数据存储方式——KVELL:快速持续键值存储的设计与实现