数据库结构的多层嵌套关系高性能读取
作者:秋了秋 发表时间:2017年08月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的并列请求,同步请求以及混搭,无空闲等待,最大化利用线程,是个好工具。