- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章浅谈mysql 树形结构表设计与优化由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
在诸多的管理类,办公类等系统中,树形结构展示随处可见,以“部门”或"机构"来说,接触过的同学应该都知道,最终展示到页面的效果就是层级结构的那种,下图随机列举了一个部门的树型结构展示图 。
稍稍有点开发和表结构设计经验的同学,设计出这样一张表,应该很容易,只需要在depart表中,添加一个pid/字段即可满足要求,参考下表:
CREATE TABLE `depart` ( `depart_id` varchar(32) NOT NULL COMMENT "部门ID", `pid` varchar(32) NOT NULL DEFAULT "0" COMMENT "组织父ID", `name` varchar(64) NOT NULL COMMENT "部门名称", `description` varchar(512) DEFAULT NULL COMMENT "部门描述", `code` varchar(64) DEFAULT NULL COMMENT "部门编码", PRIMARY KEY (`depart_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
上图是一个通用的树状结构示意图,适合大多数的业务场景,以此为例,如果“部门”不是单独的存在,与部门相关的业务主要包括下面几点:
关于第一点,第二点再做几点额外的补充,全量加载和动态加载的实现都可以,在小编历经的项目或产品中都有见到,实在是要看产品的设计和客户的要求,因为全量和动态加载的不同设计也会带来与之相对结果 。
举例来说,全量加载的好处是,数据一次性的返回给页面,页面做了渲染之后存缓存,后续再次加载的时候速度非常快,同时,类似下面这种搜索效率就非常高,因为不需要与接口交互啊 。
但问题也随之而来了,部门数据不是一成不变的,增删改的操作也是常有的事情,设计成全量加载,意味着初次查询的时候,一旦数据量超大,层级非常深,假如页面还需要渲染部门下关联的用户数据时,这个对服务端的压力就非常大了,稍有经验的同学应该能大概想到这个服务端的返回数据结构了吧 。
下面给出初步的实现思路 。
function(currentDepart_id){ 1、查找当前部门 DB ...... 2、查找当前部门的子级部门 DB ...... 3、以当前部门的子部门列表为根基进行遍历,递归查询,包装返回数据 DB ......}
从以上的代码实现来看,数据量上去之后,预估查询将会成为性能瓶颈,而且在小编的项目开发中,做过类似的测试,3个层级,每层1000条数据(未计算部门下关联用户的数据加载),在4核16G的服务器上(CPU性能普通),完成一次全量的数据加载平均在3秒左右,这个对于B端的产品,这种设计加上这种延时,用户还能接受(1000个部门,这种数据量是比较大的了) 。
上面分析到,全量加载的性能瓶颈在于数据库的IO,试想,查询的时候,从顶级节点或者某个节点算起,数据量越大,层级越深,查询的次数就越多,IO的开销自然就越大 。
解决的办法是什么呢?实践过程中,有2个经验可以参考:
关于第一点,也是大家容易想到的,但如何设计才比较合理呢?以下面的这张图为例,我们可以考虑以非叶子节点为key,而叶子节点下面的集合为value,将所有的value存入一个redis的集合中,这种考虑来源于实际业务中,用户的需求验证,即真正那些具有实际意义的部门或机构数据都分布在叶子节点上面 。
如此一来,编码的实现上面,也许可以改造成下面这样, 。
1、部门新增functiob add(params){ 1、depart入库 DB ...... 2、判断当前的depart的层级,是否叶子节点(是否即将成为叶子节点) if(叶子节点){ 3、寻找上级节点ID,并查询redis中的key 4、取出上级key对应的缓存集合,加入当前新增的part_id } else { 5、创建一个新的key,即一个新的缓存空集合,等待后续数据添加(也可以不创建) }}2、删除部门functiob delete(params){ 1、depart自身的删除 DB ...... 2、如果当前部门下存在子集部门,是否需要一起删除子部门(结合自身的产品业务) DB...... 获取所有的非叶子节点集合 3、假设第二步成立,那么还需要以当前部门节点创建的key,并取出key中的list集合,一起进行删掉 Redis操作 拿到第二步中的所有非叶子节点集合,组装成key,循环遍历删除key(内存型操作,性能不是问题,也可以做异步)}
全量加载结合redis是突破性能瓶颈的关键步骤,但从上面的实现上看,从编码的复杂性上确实有所提升,而且对开发者的编码要求有一定高度,但这种实现之后,可以说很大程度上将会提升查询的性能 。
优化查询性能的第二种考虑,表结构的改造 。
不少同学有疑问,表结构的改造对于性能影响能有多大呢?说出来可能你不信,模拟数据压测的时候,不采用改造后的实现,利用 5个层级的部门,每个部门1000的数据量(我指的是每个层级的每个部门数据量都是1000,大家可以计算下数据总量),每个部门下关联了500个用户,这样的数据量的最后表现是5分钟左右 。
看来,数据量上去了之后,查询压力确实很大,利用改造后的设计和测试效果,最终同样的数据表现,平均在15~20秒之间,这个直接是10倍多的提升,或许在我说出答案之前,也有不少同学用过,但是没有真正体会到它的妙处 。
在本文开始的表结构基础上面,我们加一个path字段,这样改造后的表如下:
CREATE TABLE `depart` ( `depart_id` varchar(32) NOT NULL COMMENT "部门ID", `pid` varchar(32) NOT NULL DEFAULT "0" COMMENT "组织父ID", `name` varchar(64) NOT NULL COMMENT "部门名称", `description` varchar(512) DEFAULT NULL COMMENT "部门描述", `code` varchar(64) DEFAULT NULL COMMENT "部门编码", PRIMARY KEY (`depart_id`), `path` varchar(128) NOT NULL COMMENT "部门路径",) ENGINE=InnoDB DEFAULT CHARSET=utf8;
这个path字段意义重大,通常的表现是,从第一个层级开始,每个层级假设最多可容纳10000个部门,这样第一层的数据大概长这样, 00001 ,00002,00003 … 往上依次累加,而第二层级,假如我们在00002这个部门下新增第二层级的部门时,数据表现大概长这样, 00002/00001 , 00002/00002 , 00002/00003 …往上依次累加 。
那么更深的层级,我就算不举例想必大家也能自行列举出后面的结构来 。
这么做有什么好处呢?
我们知道,mysql是支持正则表达式函数的,还有就是like,试想,我们要想一次性查询出从某个层级开始下面的所有的层级数据时,假如没有path这个字段,会怎么做呢?很明显,就是上文所说的通过递归了 。
但是有了path字段之后,我们可以直接利用mysql的正则表达式函数,,仍以上面的数据为例,通过下面的这两种sql,一次性的可以将一级部门(测试)这条数据的所有子集数据全部查出来,这样一来,可以说大大减少了与数据库的交互次数 。
此种实现容易踩坑,或者实际操作中比较容易出问题的地方在路径规则的生成上面,通常需要提前自定义一个函数,专门用户生成path,只要确保生成的path字段数据准确无误,这种实现从优化查询的性能提升上面,是很大的突破,小编所在的开发项目中,使用的便是这种方式 。
function generatePath(pid){ 1、pid是否为顶级 2、获取父级部门的depart 3、列举出父级部门下与当前即将新增的部门同级的所有path字段 4、取出第三步中的path最大值 5、根据第四步的path最大值生成新的path}
另外一个比较难啃的业务是,以path字段的设计之后,做部门数据的excel导入时,这个path的处理仍然是个比较复杂的实现点,这一点留待大家思考.
以上探讨了全量加载下,从业务实现到代码设计层面的优化 , 以及表结构设计层面优化的2个方面做了比较深入的探讨,而动态加载的实现,相对来说,可以说在上面这两种实现方案的基础上,稍作引用即可,难度更小 。
总结下来,这里推荐一个关于这种带有层级结构形状的业务设计上的最佳实践, 。
表结构上,采用path字段数据加载上,尽量使用动态加载如果部门(层级结构的业务)变动不大,可以考虑引入缓存,具体实践参考本文上面所说 。
到此这篇关于浅谈mysql 树形结构表设计与优化的文章就介绍到这了,更多相关mysql 树形结构表优化内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://blog.csdn.net/zhangcongyi420/article/details/120387876 。
最后此篇关于浅谈mysql 树形结构表设计与优化的文章就讲到这里了,如果你想了解更多关于浅谈mysql 树形结构表设计与优化的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我目前正在尝试基于哈希表构建字典。逻辑是:有一个名为 HashTable 的结构,其中包含以下内容: HashFunc HashFunc; PrintFunc PrintEntry; CompareF
如果我有一个指向结构/对象的指针,并且该结构/对象包含另外两个指向其他对象的指针,并且我想删除“包含这两个指针的对象而不破坏它所持有的指针”——我该怎么做这样做吗? 指向对象 A 的指针(包含指向对象
像这样的代码 package main import "fmt" type Hello struct { ID int Raw string } type World []*Hell
我有一个采用以下格式的 CSV: Module, Topic, Sub-topic 它需要能够导入到具有以下格式的 MySQL 数据库中: CREATE TABLE `modules` ( `id
通常我使用类似的东西 copy((uint8_t*)&POD, (uint8_t*)(&POD + 1 ), back_inserter(rawData)); copy((uint8_t*)&PODV
错误 : 联合只能在具有兼容列类型的表上执行。 结构(层:字符串,skyward_number:字符串,skyward_points:字符串)<> 结构(skyward_number:字符串,层:字符
我有一个指向结构的指针数组,我正在尝试使用它们进行 while 循环。我对如何准确初始化它并不完全有信心,但我一直这样做: Entry *newEntry = malloc(sizeof(Entry)
我正在学习 C,我的问题可能很愚蠢,但我很困惑。在这样的函数中: int afunction(somevariables) { if (someconditions)
我现在正在做一项编程作业,我并没有真正完全掌握链接,因为我们还没有涉及它。但是我觉得我需要它来做我想做的事情,因为数组还不够 我创建了一个结构,如下 struct node { float coef;
给定以下代码片段: #include #include #define MAX_SIZE 15 typedef struct{ int touchdowns; int intercepti
struct contact list[3]; int checknullarray() { for(int x=0;x<10;x++) { if(strlen(con
这个问题在这里已经有了答案: 关闭 11 年前。 Possible Duplicate: Empty “for” loop in Facebook ajax what does AJAX call
我刚刚在反射器中浏览了一个文件,并在结构构造函数中看到了这个: this = new Binder.SyntaxNodeOrToken(); 我以前从未见过该术语。有人能解释一下这个赋值在 C# 中的
我经常使用字符串常量,例如: DICT_KEY1 = 'DICT_KEY1' DICT_KEY2 = 'DICT_KEY2' ... 很多时候我不介意实际的文字是什么,只要它们是独一无二的并且对人类读
我是 C 的新手,我不明白为什么下面的代码不起作用: typedef struct{ uint8_t a; uint8_t* b; } test_struct; test_struct
您能否制作一个行为类似于内置类之一的结构,您可以在其中直接分配值而无需调用属性? 前任: RoundedDouble count; count = 5; 而不是使用 RoundedDouble cou
这是我的代码: #include typedef struct { const char *description; float value; int age; } swag
在创建嵌套列表时,我认为 R 具有对列表元素有用的命名结构。我有一个列表列表,并希望应用包含在任何列表中的每个向量的函数。 lapply这样做但随后剥离了列表的命名结构。我该怎么办 lapply嵌套列
我正在做一个用于学习目的的个人组织者,我从来没有使用过 XML,所以我不确定我的解决方案是否是最好的。这是我附带的 XML 文件的基本结构:
我是新来的 nosql概念,所以当我开始学习时 PouchDB ,我找到了这个转换表。我的困惑是,如何PouchDB如果可以说我有多个表,是否意味着我需要创建多个数据库?因为根据我在 pouchdb
我是一名优秀的程序员,十分优秀!