解决 mysql 树形结构插入数据查询死循环问题

时间:2024-03-28 14:05:44

在项目中,大家可能会遇到这样一个问题,就是当你操作那些具有上下级的树的表的时候,如果是单纯的父子级关系,可能不会碰见这个问题,但是如果这个看起来具有树形结构的表形成闭环的时候,问题就来了,我遇到的结果就是,一旦碰到这样的情况,就是页面一直卡在那里,对应着后台就是,要么程序死循环,要么数据库死循环,直到系统卡死崩溃。

这个问题很是头疼,在网上找了不少资料没有发现真正能够解决我的问题的,究其原因,还是这样的树形结构用的少,平常大家使用的多的还是那种无限层级的,也就是父子级可以无限下去的,这里就来看看我这个问题和解决办法吧。

首先数据库里面有一张表,其中有两个很关键的字段,targetIden和srcIden,这两个字段用于标记理论上可作为父子级的一条关系数据,可以理解成targetIden是父节点数据,srcIden是子节点数据,如图所示,

解决 mysql 树形结构插入数据查询死循环问题
在这个表中我创建了几条测试数据,相信大家可以看出来,已经形成了一条联试结构的数据,也就是两两之间存在某种隐含的父子级关系,现在假如来了一个需求,需要创建一条尾号为 …55 和 …411的数据,也就是插入一条这样的数据,可以吗?
解决 mysql 树形结构插入数据查询死循环问题

这个很简单,这需要执行一下插入即可,没问题,可以插入成功,问题就在这时候产生了,当这条数据插入成功后,我们在进行查询,比如以411为条件查询所有的关系数据,这时候悲催的问题就产生了,不管是用程序递归还是数据库的递归来查询,都会造成死循环,其实两者查询本质一样,都是递归,但是结果都是死循环儿查不出结果来,因为从数据结构上来说,形成了一条闭环的数据链路;

解决的思路无非有两种,
1、通过其他的方式查询出这个链路的数据【这个我想了不少办法,还是没有很好的解决方式】
2、插入数据的时候将这个问题规避掉;

对比了一下实现成本,第二张方式相对简单一点,下面就用第二种方式实现以下;

既然选择了第二种思路,我们先思考一下,在插入数据的时候,我们该怎么避免这个死循环的结构数据产生呢?

还是以上面的用555和411为例,我们插入411和555的关系之前,对于555这条数据,是不是需要把插入之前555的整个上下链路的所有数据查出来,查出来以后呢?只需要判断一下对应的411这条数据是否存在于555的前置链路或者后置链路上不久可以了吗

解决方法就在此,插入之前,我们把555之前的链路数据全部查询出来,555之后的数据全部查出来,然后基本上就出来了,下面看具体代码,主要就是一个方法类,

/**
     * 判断是否存在死循环节点
     * @param params
     * @return
     */
    public boolean isDeadNodes(Map params){
        String targetIden = params.get("targetIden").toString();
        String srcIdne = params.get("srcIden").toString();
        List<String> exists = this.baseDao.queryForListBySql("fieldanalysisMapping.existTreesOld",params);
        if(exists != null && exists.size() >0){
            //存在关联的数据了,不允许存在
            return true;
        }

        //如果父节点为空,可以直接进行插入数据
        //1、查找父节点是否已经存在了?
        List<String> targetIds = this.baseDao.listHql("select id from MdRelation where targetIden =  '"+targetIden+"' or srcIden = '"+targetIden+"'");

        List<String> beforesLeft = new ArrayList<>();
        List<String> aftersRight = new ArrayList<>();

        if(targetIds == null || targetIds.size() ==0){
            // 父节点根本不存在
            return false;
        }else {
            //父节点存在,查找父节点的所有前置节点   ===  影响关系数据
            Map beforeMap = new HashMap();
            beforeMap.put("fieldIden",targetIden);

            List<Map<String,String>> beforList =
                    this.baseDao.queryForListBySql("fieldanalysisMapping.getFieldsEffectsForFunction", beforeMap);
            if(beforList != null && beforList.size() >0){
                for(Map one:beforList){
                    beforesLeft.add(one.get("TARGET_IDEN").toString());
                    beforesLeft.add(one.get("SRC_IDEN").toString());
                }
            }

            //再找到后置节点数据
            Map afterMap = new HashMap();
            afterMap.put("fieldIden",targetIden);
            List<Map<String,String>> aftersList =
                    this.baseDao.queryForListBySql("fieldanalysisMapping.getFieldsBloodsForFunction", beforeMap);
            if(aftersList != null && aftersList.size() >0){
                for(Map one:aftersList){
                    aftersRight.add(one.get("TARGET_IDEN").toString());
                    aftersRight.add(one.get("SRC_IDEN").toString());
                }
            }
        }
        List<String> srcIds = this.baseDao.listHql("select id from MdRelation where targetIden =  '"+srcIdne+"' or srcIden = '"+srcIdne+"'");
        //如果子节点存在,
        if(srcIds == null || srcIds.size() ==0){    //子节点根本不存在
            return false;
        }else{
            //子节点存在了,判断是否存在于父节点的前置或者后置节点上,影响关系是后置,血缘关系是前置

            if(beforesLeft == null || beforesLeft.size() ==0){
                //如果前置节点为空
                if(aftersRight != null && aftersRight.size() >0){
                    //后置节点存在
                    if(aftersRight.contains(srcIdne)){
                        return true;
                    }else{
                        return true;
                    }
                }
            }else{      //如果前置节点不为空
                if(aftersRight==null ||aftersRight.size()==0){ //如果后置节点为空
                    if(beforesLeft.contains(srcIdne)){
                        return true;
                    }else{
                        return false;
                    }
                }else{  //前后节点都存在
                    if(aftersRight.contains(srcIdne) && !beforesLeft.contains(srcIdne)){
                        return false;
                    }
                    if(!aftersRight.contains(srcIdne) && beforesLeft.contains(srcIdne)){
                        return true;
                    }
                }
            }
        }
        return false;
    }

方法中对应的几个查询语句的mybatis方法也列举在下面,这里采用了数据库递归的方式,即采用了with - as函数的写法,分别查询555这条数据的前置和后置节点,

后置节点数据,

<select id="getFieldsBloodsForFunction" parameterType="java.util.Map" resultType="java.util.Map">
         WITH PPL (ID,TARGET_IDEN,SRC_IDEN,RULE_TYPE,RULE_DESC) AS
        (
          SELECT ID,TARGET_IDEN,SRC_IDEN ,RULE_TYPE,RULE_DESC FROM MD_RELATION WHERE TARGET_IDEN = #{fieldIden}
          UNION ALL
          SELECT child.ID,child.TARGET_IDEN,child.SRC_IDEN ,child.RULE_TYPE, child.RULE_DESC FROM PPL parent,
                MD_RELATION child WHERE child.TARGET_IDEN = parent.SRC_IDEN
        )
        SELECT * FROM PPL
    </select>

前置节点数据:

 <select id="getFieldsEffectsForFunction" parameterType="java.util.Map" resultType="java.util.Map">
        WITH PPL (ID,TARGET_IDEN,SRC_IDEN,RULE_TYPE,RULE_DESC) AS
        (
          SELECT ID,TARGET_IDEN,SRC_IDEN ,RULE_TYPE,RULE_DESC FROM MD_RELATION WHERE SRC_IDEN = #{fieldIden}
          UNION ALL
          SELECT child.ID,child.TARGET_IDEN,child.SRC_IDEN ,child.RULE_TYPE, child.RULE_DESC FROM PPL parent,
                MD_RELATION child WHERE child.SRC_IDEN = parent.TARGET_IDEN
        )
        SELECT * FROM PPL
    </select>

解决 mysql 树形结构插入数据查询死循环问题

解决 mysql 树形结构插入数据查询死循环问题

因为我们的目标数据是555和411的关系,所有555的前置和后置的关系数据结构就显示成如上的结构,然后我们调用一下接口看看效果,可以看到我们已经将这条数据拦截到了,同样,我们再测试一个,

解决 mysql 树形结构插入数据查询死循环问题
我们使用96和38这条数据测试,因为他们也是可能造成死循环的树,
解决 mysql 树形结构插入数据查询死循环问题
可以看到也拦截到这对可能造成死循环的数据了,
解决 mysql 树形结构插入数据查询死循环问题

通过上面的方式基本上达到了预期的效果,希望对看到的小伙伴有所有帮助!