如何在关系数据库中存储带有孩子的链接树?

Posted

技术标签:

【中文标题】如何在关系数据库中存储带有孩子的链接树?【英文标题】:How to store Linked Tree with children in Relational Database? 【发布时间】:2018-11-07 03:38:32 【问题描述】:

我有一个带有子(节点)的自定义 LinkedTree,并且节点具有邻接关系,即每个节点都与前一个和下一个链接。这个 LinkedTree 很重很大,它可能包含数百万个节点。

这是一个代码示例:

package tree;

import java.io.Serializable;

public class LinkedTree<E> implements Serializable 

    private int size = 0;
    private Node<E> first;
    private Node<E> last;
    private LinkedTree<E> children;

    public LinkedTree() 
        children = new LinkedTree<>();
    

    public LinkedTree(LinkedTree<E> children) 
        this.children = children;
    

    public void add(E element) 

        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, element, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
    

    public void remove(E element) 

        ...
    

    public int size() 

         return size;
    

    public static class Node<E> implements Serializable 

        E item;
        Node<E> next;
        Node<E> prev;

        public Node(Node<E> prev, E item, Node<E> next) 

            this.item = item;
            this.next = next;
            this.prev = prev;
        

        public E item() 

            return item;
        

        public boolean hasPrevious() 

            return prev != null;
        

        public Node<E> previous() 

            return prev;
        

        public Node<E> previous(int target) 

            Node<E> n = this;
            int i = target;
            while (i-- > 0 && n != null)
                n = n.prev;
            return n;
        

        public boolean hasNext() 

            return next != null;
        

        public Node<E> next() 

            return next;
        

        public E nextItem() 

            return next.item;
        

        public E nextItem(int target) 

            return next(target).item();
        

        public Node<E> next(int target) 

            Node<E> n = this;
            int i = 0;
            while (i++ < target && n != null)
                n = n.next;
            return n;
        

        @Override
        public int hashCode() 

            return item != null ? item.hashCode() : 0;
        

        @Override
        public boolean equals(Object o) 

            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            Node<?> node = (Node<?>) o;

            return item != null ? item.equals(node.item) : node.item == null;
        

        @Override
        public String toString() 

            return item.toString();
        
    

我想对其进行序列化并将其保存在文件中以使其持久化,但加载和写入一些数据可能成本太高。所以我决定把它保存在 mysql 中,这样我就可以从任何我想要的地方加载数据。我的意思是从这个层次结构的末端、中间或开始。

我想该行的关系应该同时处于邻接关系和父子关系。但我不知道该怎么做。

【问题讨论】:

您是否考虑过像OrientDB 或Neo4J 这样的图形数据库?像这样的东西可能更适合您的要求。 大约一年前我都使用过它们。您是最适合解决此问题的图形数据库。但是还有一个问题,我已经在 MySQL 中存储了一些原始数据,在两种数据库中存储数据对我来说有点奇怪 没有简单的答案 - 选择取决于预期的使用模式。这个关于分层数据的 SQL 模型的精彩演示 slideshare.net/billkarwin/models-for-hierarchical-data 可能会有所帮助。或谷歌Joe Celko "SQL FOR SMARTIES" 【参考方案1】:

我会评论要求提供更多信息(特别是样本数据和层次结构规则),所以请原谅第一次剪辑过于笼统

我做了以下假设:

你的结构大于三层深度 - 如果不是这种情况,那么我不会这样做,因为它不值得

您的负载是读取繁重的,并且对树的某些部分的写入是并发的但不冲突,而冲突的写入很少或不存在

您不需要对树进行并行、部分和无共享或无锁访问来构建或处理它(在这种情况下,您需要发送信号 deletes,您可以通过指向您替换的节点,即取代)

我提出的数据模型如下所示:

create table treemodel (
 `node` int not null 
 , `parent` int not null 
 , `principal` int not null 
 , `state` smallint unsigned not null 
 , ...
 , `supersedes` int    /*version instead of lossy update or delete*/
 , `supersededby` int
) engine = innodb;
alter table treemodel add primary key (`principal`, `node`) using btree; 

    treemodel 表将仅包含结构标识符:我会将节点数据保存在单独的表中,但我不会加入以获取它,而是执行第二个 select ... where node in (...) - 这基本上是说'我的数据结构独立于我的数据'

    此模型旨在限制到数据库的往返次数,可能看起来违反直觉,但允许您在没有连接的单个数据库指令中读取或锁定树的部分

    这将与您的数据模型背道而驰,因为您不会在嵌套子节点中存储额外的“主要”节点 - 但如果您可以更改此结构,那么您可以利用此建议来避免查询在循环内,即。多个selects,或重入查询或自/一元连接

    ...但是您的业务逻辑需要支持我称之为“主要”节点的概念

    这取决于您的用例将什么放入主节点 - 我已经使用此模型来存储观察记录及其派生之间的因果关系,而不管此点以下的父子关系如何 - 另一个例如:1) 客户提出支持案例,或 2) 发送新消息,或 3) 创建新文件,...

    将主体存储为树结构中的实际根节点是没有意义的(即节点 id '1' 或 'data directory' 或其他) - 相反,您将存储 'next为了争论而降低水平,即。 “用户根目录”或“用户根目录下的第一级” - 了解您的用例和业务规则会有所帮助

    ... 并且您的 java 代码将需要更新以查找或复制和存储此概念 - 它始终是树的给定分支内任何 insert 上的父节点的副本 -如果您要移动分支并且您的逻辑需要更改此数字,则它是 update ... set principal=y where principal=xupdate ... set parent=newid where node=current_principal

    ... 说了这么多,我不会更新行本身,只更新insert 并重新创建整个 分支(解释了state 字段,即CURRENTDELETED,...其中已删除分支的根节点仍指向其当前父节点,例如“已删除项目”)

    你仍然在 prev 和 next 中保留指向每个相邻节点的指针 - 在最坏的情况下,将节点有序插入到树的分支中,需要 select ... where principal=x for update,但可能只需要 select ... where (node=x or prev=x or next=x) for update

编辑:

主键/聚集索引必须是唯一的 - 您也可以在 principal 上进行分区以确保快速并行访问

重新创建而不是更新分支

【讨论】:

@ste7 这里有更多解决方案,其中一个是由在其他 cmets 中链接的幻灯片共享中提出闭包模型的同一个人创作的:(***.com/q/192220/4036945) - 出于某种原因我无法评论您的问题,只能回答我的问题!? 谢谢。我认为您可以在答案末尾添加此链接作为附加信息。【参考方案2】:

一年前我有同样的要求,然后我探索了很多选择。然后我决定使用图形数据库。试用 Neo4j,一个图形数据库。

【讨论】:

我之前用过Neo4J,写几百万行速度很慢。我更喜欢 OrientDB 作为 Grapch 数据库。

以上是关于如何在关系数据库中存储带有孩子的链接树?的主要内容,如果未能解决你的问题,请参考以下文章

如何在关系数据库中存储 trie?

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

如何隐藏“孩子”:[]从弹簧靴中一对多关系中的最后一个孩子

数据结构(二十一)二叉树遍历算法的应用与二叉树的建立

数据结构(二十二)二叉树的顺序存储结构

大话数据结构(十五)——二叉树的理论知识