当前位置:秋叶网络博客 前端设计 ◊ 数据库结构的多层嵌套关系高性能读取

数据库结构的多层嵌套关系高性能读取

作者:秋叶 发表时间:2017年8月09日

博客转nodejs已经开工一周了,每天晚上利用闲暇的几个小时完成几个功能,目前进度已完成一半了,架构已成型,前台页面的渲染已经差不多了,接下来很快进入后台管理界面,前后端都用js写,高吞吐高并发就是这么任性,高雅的回调和异步就是这么美丽。总的来说与我预期的相差不会很远,稍微难了一些。期间遇到的典型问题我会写博记录下来,可供他人与我后期翻阅。

今天写的是数据库结构的多层嵌套高性能读取,比如说评论楼层的嵌套,你@我,我@你,可以无限级@回复,各评论之间达到一种继承关系。wordprss对这种情况的数据库设计是在每个评论加个parent字段来指示继承关系,这种设计对写数据就很容易,但是你要以树形关系读出数据就不那么简单了。

1.最开始想的是每读一个parent的评论就再开个查询搜索其子评论,层数多以及评论多的情况下这是对数据库的致命打击,不可取。

2.再者是想并发查询,可是父评论与子评论的查询又有依赖关系,不可取。

3.然后是想着把这两种类型的评论都一起查出来,可是对于评论分页搞不了,不可取。

最终选择做两次查询,一次是全部查出父级(顶级)评论,第二次是全部查出子级评论,最后通过递归集中处理把这些嵌套一一对应插进去,毕竟算法的速度比无限开io查数据快太多,假如查出两组数据:

    var responses = [
        {
            name:"这是个好数组",
            id:25,
            parent:0
        },
        {
            name:"你是250",
            id:250,
            parent:0
        }
    ];
    var others = [
        {
            name:"你是baby",
            id:2,
            parent:32
        },
        {
            name:"你是bob",
            id:3,
            parent:2
        },
        {
            name:"你很2",
            id:30,
            parent:2
        },
        {
            name:"我也是",
            id:32,
            parent:25
        }
    ];

parent字段表示它属于哪个id的子集。
数据处理如下(前后端处理通用):

function inheritParent(parents,childrens){//parents全部父级元素的集合,childrens全部子级元素的集合
    for(var i = childrens.length - 1; i >= 0; i--){
        for(var j = parents.length - 1;j >= 0; j--){
            parents[j].childrens || (parents[j].childrens = []);//此字段强制要求有,没有也给个空数组
            if(childrens[i]){
                if(childrens[i].parent === parents[j].id){
                    if(parents[j].childrens.indexOf(childrens[i])==-1){
                        parents[j].childrens.push(childrens[i]);
                    }
                    childrens.splice(i,1);//插入后把其删除,节约循环次数,在大数据面前性能提高将会非常明显
                }else if(parents[j].childrens.length){
                    arguments.callee(parents[j].childrens,[childrens[i]]);//递归查询
                }
             }
        }
    }
}
inheritParent(others,others);//子集间可能也会互相嵌套,先把它们继承整合
inheritParent(responses,others);

注意加红色的字段替换成你的数据字段名。处理之后,只要是子集都会合并到父级中去,并且子集会去掉。结合实际数据库操作可参考我的路由处理方法代码:

function findComments(post_id,page,showunapproved,callback){
    if(arguments.length === 3){
        callback = showunapproved;
        showunapproved = undefined;
    }
    var response = {},comment_ids = [];
    var approved = showunapproved?[1,0]:[1];
    var find = {"comment_post_ID":post_id,"comment_approved":{$in:approved},"comment_parent":0};

    function getcomments(haveParent,response,callback){

        var show = {"_id":0,"comment_ID":1,"comment_author":1,"comment_author_url":1,"comment_date":1,"comment_content":1,"comment_approved":1,"comment_agent":1,"comment_parent":1};
        var _find = fn.extend(true,{},find);
        _find.comment_parent = haveParent?{$ne:0}:0;
        db.find(setting.doc.comments,haveParent?{}:{"sort":{"comment_date":-1},"page":page?(page-1):page,"pageamount":10},_find,show, function (err, result) {
            if(err){
                response.success = false;
                response.lists = [];
            }else{
                response.success = true;
                response[haveParent?"childrens":"parents"] = result;
            }
            if(!result || !result.length){
                return callback(err,response);
            }
            result.forEach(function(item){
                comment_ids.push(item.comment_ID);
            });
            callback();
        });
    }

    async.parallel({
        one:function(_callback){
            db.getAllCount(setting.doc.comments,find,function(count){
                response.amount = count;
                _callback(null,1);
            });
        },
        two:function(_callback){
            async.waterfall([
                function(call){
                    async.parallel({
                        "haveParent":function(_call){
                            getcomments(1,response,function(){
                                _call(null,1.1);
                            });
                        },
                        "haveNotParent":function(_call){
                            getcomments(0,response,function(){
                                _call(null,1.2);
                            });
                        }
                    },function(err){
                        if(err){
                            response.success = false;
                            response.message = err;
                            return callback(err,response);
                        }
                        call(null,comment_ids);
                    });

                },
                function(lists,call){
                    response.lists = response.parents.concat(response.childrens);
                    db.find(setting.doc.commentsmeta,{},{"comment_id":{$in:lists},"meta_key":"city_name"},{"comment_id":1,"_id":0,"meta_value":1}, function (err, result) {
                        if(err){
                            response.success = false;
                            response.message = err;
                            return callback(err,response);
                        }
                        response.lists.forEach(function(item){
                            item.city = "火星";
                            result.forEach(function(_item){
                                if(item.comment_ID === _item.comment_id){
                                    item.city = _item.meta_value;
                                }
                            });
                        });
                        fn.inheritParent(response.childrens,response.childrens);
                        fn.inheritParent(response.parents,response.childrens);
                        response.lists = response.parents;
                        call(null,2);
                    });
                }
            ],function(err){
                if(err){
                    response.success = false;
                    response.message = err;
                    return callback(err,response);
                }
                _callback(null,2);
            });
        }
    },function(err){
        callback(err,response);
    });
}

当然,让这个处理放在客户端更加好,缺点是子集评论,无论是多少级的,要一次性全部查出,如有更优的数据库查询大法望奔走相告。另外说一句,async让我写node很爽,这个代码片段包括async的并列请求,同步请求以及混搭,无空闲等待,最大化利用线程,是个好工具。

目录: 前端设计 | 标签: , | 1677次阅读