关于无限级功能的实现与优化

简介: 关于无限级功能的实现与优化

无限级

在程序设计中,有很多需求都是无限级的,例如:无限级菜单,邀请人,目录无限级,地区分类,等等

image.png

无限级的实现方案

最简单的实现方案则是增加一个pid,即可实现无限级:

## data_list
- id \`int\`
- pid \`int\`
- name \`string\`
``````sql
CREATE TABLE \`test\`.\`data_list\`  (
  \`id\` int(0) NOT NULL AUTO_INCREMENT,
  \`pid\` int(0) NULL COMMENT '上级id',
  \`name\` varchar(255) NULL COMMENT '名称',
  PRIMARY KEY (\`id\`),
  INDEX \`idx_pid\`(\`pid\`)
);

插入n条测试数据:

insert into data_list(id,pid,name) values(1,0,'顶级测试1');
insert into data_list(id,pid,name) values(2,0,'顶级测试2');
insert into data_list(id,pid,name) values(3,0,'顶级测试3');
insert into data_list(id,pid,name) values(4,1,'2级1测试1');
insert into data_list(id,pid,name) values(5,2,'2级2测试2');
insert into data_list(id,pid,name) values(6,2,'2级2测试3');
insert into data_list(id,pid,name) values(7,5,'3级5测试1');
insert into data_list(id,pid,name) values(8,7,'4级7测试1');

获取id8的上级代码为:

我们只需要查询出id 8 的消息,然后根据pid就可以找到id8的上级

package main
import (
   "fmt"
   _ "github.com/go-sql-driver/mysql"
   "github.com/jinzhu/gorm"
)
type dataStruct struct {
   Id   int
   Pid  int
   Name string
}
func main() {
   dbConn, err := gorm.Open("mysql", "root:123456@tcp(localhost:3306)/test?charset=utf8&parseTime=True&loc=Local")
   if err != nil {
      panic(err)
   }
   defer dbConn.Close()
   id := 8
   data := getInfo(dbConn, id)
   fmt.Println("data:", data)
   parentData := getInfo(dbConn, data.Pid)
   fmt.Println("parentData:", parentData)
}
func getInfo(db \*gorm.DB, id int) (data \*dataStruct) {
   data = &dataStruct{}
   db.Table("data_list").Where("id = ?", id).First(&data)
   return data
}

下面的go代码将简写,不再复制import代码,数据库初始化代码以及结构体

获取id2的直属下级的代码为:

我们只需要数据库查询pid=2的,即是2的下级

func getChildList(db *gorm.DB, id int) (data \[\]dataStruct) {
   db.Table("data_list").Where("pid = ?", id).Find(&data)
   return data
}
func main() {
   id := 2
   list := getChildList(dbConn, id)
   fmt.Println(list)
}

如何获取id8的所有上级呢?

同上,我们先查出8的上级,再通过8的上级pid再查,如此往复.....

我们需要不断的循环递归进行获取:

func main() {
   id := 8
   ids := getParentIds(dbConn, id, \[\]int{})
   fmt.Println(ids)
}
func getParentIds(db *gorm.DB, id int, ids \[\]int) \[\]int {
   ids = append(ids, id)
   //获取id的信息
   data := getInfo(db, id)
   //获取父级id
   parentId := data.Pid
   if parentId > 0 {
      return getParentIds(db, parentId, ids)
   }
   return ids
}
func getInfo(db \*gorm.DB, id int) (data \*dataStruct) {
   data = &dataStruct{}
   db.Table("data_list").Where("id = ?", id).First(&data)
   return data
}

获取id2的所有下级(下级的下级)

同上,我们需要先查出id2的直属下级,再遍历所有的下级的下级......

func main() {
   id := 2
   list := getAllChildList(dbConn, id)
   fmt.Println(list)
}
func getAllChildList(db *gorm.DB, id int) (data \[\]dataStruct) {
   childList := getChildList(db, id)
   childDataList := make(\[\]dataStruct, 0)
   childDataList = append(childDataList, childList...)
   for _, child := range childList {
      dataList := getAllChildList(db, child.Id)
      childDataList = append(childDataList, dataList...)
   }
   return childDataList
}
func getChildList(db *gorm.DB, id int) (data \[\]dataStruct) {
   db.Table("data_list").Where("pid = ?", id).Find(&data)
   return data
}

就这样,很简单,我们已经实现了一个传统的无限级功能!那么能优化吗?

无限级的优化

在优化之前,我们需要讨论上面的无限级到底有什么问题,要怎么优化

我们可以看出,在查询单一层级的时候,都是需要一条sql即可完成,

而在查询所有上级和所有下级时,出现了各种递归查找,如果你有1万个下级,可能需要查询1万次数据!

透过问题看本质,为什么会需要这么多次查询,该怎么优化?递归查询到底会慢在哪里?

我们可以总结为2点:

1:查询了太多次数据库,每次都有网络io的产生,并且需要等待查询完才能进行下一次查询

2:递归遍历了多次,每次查询上下级,都得去算一遍上下级关系.理论上,上下级关系一旦确定是不会再发生更改的,不需要重新计算

针对这2点问题,我们可以规划优化方案

1:只算一次,将会员的计算结果缓存

2:将整个会员数据缓存,使用时不查数据库,查redis

3:将上下级的数据缓存,需要查询时,直接根据缓存的id去 "id in" 数据库

4:通过存储过程去查询所有数据,因为没有io消耗,会非常快

5:使用节点图数据库neo4j  或者jsonb方案存储

在本文中,将说明3,4 方案的实现

存储过程优化

存储过程优化点在于,可以节省程序查询数据库的部分,节省大量的io,从而提高查询速度:

查询所有下级:

DROP PROCEDURE IF EXISTS \`get\_child\_ids\` -- 如果存在,则删除存储过程
CREATE DEFINER=\`root\`@`%` PROCEDURE \`get\_child\_ids\`(dataId INT(11))
BEGIN
    #Routine body goes here...
    declare ids LONGTEXT default '';
    declare parentId INT(11) default '0';
    declare childIds LONGTEXT default '';
    declare oldChildIds LONGTEXT default '';
    declare i int default 0;
    SET SESSION group\_concat\_max_len=102400;
    select group\_concat(id) into childIds from data\_list where pid = dataId;
    set @i=1;
    -- select "开始循环",childIds;
    WHILE childIds != '' DO
         set oldChildIds = childIds;
         set ids = CONCAT(ids,",",childIds);
         select group\_concat(id) into childIds from data\_list where FIND\_IN\_SET(pid,childIds);
         -- select "循环开始时childIds数据:",oldChildIds,"循环后数据",childIds,"ids",ids;
         set i = i+1;
   END WHILE;
   select ids;
   -- select "几重循环:",i;
END

调用存储过程:

call get\_child\_ids(44);

输出:


image.png

image.png

查询所有上级

CREATE DEFINER=\`root\`@`%` PROCEDURE \`get\_parent\_ids\`(dataId INT(11))
BEGIN
    #Routine body goes here...
    declare ids LONGTEXT default '';
    declare parentId INT(11) default '0';
    declare i int default 0;
    select pid into parentId from data_list where id = dataId;
    set @i=1;
    -- select "开始循环",childIds;
    WHILE parentId >0 DO
         set ids = CONCAT(ids,",",parentId);
         select pid into parentId from data_list where id=parentId;
         -- select "循环开始时childIds数据:",oldChildIds,"循环后数据",childIds,"ids",ids;
         set i = i+1;
   END WHILE;
   select ids;
   -- select "几重循环:",i;
END

调用:

call get\_parent\_ids(95474);

输出:

image.png

数据结构优化

我们在每条记录上新增 parent_data和child_data 字段,用于记录上级id以及自己下级的所有id,在查询时

只需要将id全部取出来,in操作,一条记录即可查询出所有数据:

image.png

parent_data

记录上级的id,从父级到父级的父级,以此类推

child_data

记录子级的关系节点,由json格式存储

目录
相关文章
|
4月前
|
运维 Serverless 测试技术
函数计算产品使用问题之支持10个并发任务需要多少资源
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
18天前
|
SQL 缓存 监控
接口性能倍增记:一次成功的优化实践
在软件开发过程中,接口性能优化是提升用户体验和系统稳定性的关键环节。本文将分享一次接口优化的成功案例,从问题发现到解决方案实施,详细介绍我们的优化过程和成果。
23 0
|
7月前
|
算法 API C++
使用C++进行系统级编程的深入探索
【5月更文挑战第23天】本文探讨了C++在系统级编程中的应用,强调其接近底层、高性能、可移植性和面向对象编程的优势。关键技术和最佳实践包括:内存管理(智能指针和RAII原则)、多线程(std::thread和同步原语)、系统调用与API、以及设备驱动和内核编程。编写清晰代码、注重性能、确保安全稳定及利用开源库是成功系统级编程的关键。
|
2月前
|
物联网 5G 调度
|
4月前
|
存储 监控 Serverless
函数计算发布功能问题之用户在使用主流函数计算产品的日志服务时可能会遇到使用成本的问题如何解决
函数计算发布功能问题之用户在使用主流函数计算产品的日志服务时可能会遇到使用成本的问题如何解决
|
4月前
|
编解码 弹性计算 Serverless
解锁多媒体处理新纪元:阿里云函数计算,一键驱动高效、灵活、成本优化的文件处理解决方案!
【8月更文挑战第2天】随着云计算的发展,高效灵活的多媒体处理成为必需。阿里云函数计算提供全托管服务,用户仅需上传代码,平台自动配置资源,支持毫秒级弹性伸缩。与对象存储服务集成,实现视频转码、音频提取及图片压缩等功能,按需付费降低成本。示例展示了基于Python的视频转码函数,体现其在多媒体处理领域的强大潜力和优势。
51 10
|
4月前
|
编解码 运维 Serverless
函数计算驱动多媒体文件处理:告别资源瓶颈,释放处理能力
随着多媒体内容的爆炸性增长,如何高效地处理和管理多媒体文件成为了各大企业面临的重大挑战。阿里云提供的函数计算(Function Compute)驱动多媒体文件处理解决方案,为这一问题提供了高效、灵活的解决途径。本文将对该解决方案进行详细评测,分析其优势和应用场景。
62 1
|
4月前
|
机器学习/深度学习 算法 PyTorch
深度强化学习在自动扩展云资源中的应用
【8月更文第19天】随着云计算的普及,企业越来越依赖于云服务来处理大量数据和运行复杂的应用程序。为了应对不断变化的工作负载,云提供商通常会采用自动扩展机制来动态调整资源分配。然而,这种自动扩展需要考虑成本和性能之间的平衡。传统的基于规则或阈值的自动扩展策略往往难以适应高度动态的工作负载变化。而深度强化学习(DRL)提供了一种灵活且强大的方法来优化资源分配策略,以达到最佳的成本效益比。
90 0
|
5月前
|
存储 SQL 运维
MSSQL性能调优精要:索引深度优化、查询高效重构与并发精细控制
在MSSQL数据库的运维与优化领域,性能调优是一项复杂而细致的工作,直接关系到数据库的稳定性和响应速度
|
7月前
|
缓存 监控 算法
Python性能优化面试:代码级、架构级与系统级优化
【4月更文挑战第19天】本文探讨了Python性能优化面试的重点,包括代码级、架构级和系统级优化。代码级优化涉及时间复杂度、空间复杂度分析,使用内置数据结构和性能分析工具。易错点包括过度优化和滥用全局变量。架构级优化关注异步编程、缓存策略和分布式系统,强调合理利用异步和缓存。系统级优化则涵盖操作系统原理、Python虚拟机优化和服务器调优,需注意监控系统资源和使用编译器加速。面试者应全面理解这些层面,以提高程序性能和面试竞争力。
87 1
Python性能优化面试:代码级、架构级与系统级优化