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

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

无限级

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

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格式存储

目录
相关文章
|
存储 编解码 缓存
视频平台技术成本控制的量化方法
在线视频平台为用户提供服务时,面临的一个严重的挑战是,如何保证在为用户提供流畅 且稳定播放服务的前提下,尽量降低整体运营成本。本篇文章将围绕上述问题,重点讨论技术实践中的成本控制手段。
视频平台技术成本控制的量化方法
|
6月前
|
算法 API C++
使用C++进行系统级编程的深入探索
【5月更文挑战第23天】本文探讨了C++在系统级编程中的应用,强调其接近底层、高性能、可移植性和面向对象编程的优势。关键技术和最佳实践包括:内存管理(智能指针和RAII原则)、多线程(std::thread和同步原语)、系统调用与API、以及设备驱动和内核编程。编写清晰代码、注重性能、确保安全稳定及利用开源库是成功系统级编程的关键。
|
2月前
|
弹性计算 关系型数据库 Serverless
告别资源瓶颈,函数计算驱动多媒体文件处理方案:https://www.aliyun.com/solution/tech-solution/fc-drive-file
本文介绍了一种基于阿里云的一键部署解决方案,利用云服务器ECS、RDS MySQL、OSS、函数计算FC及MNS等服务,实现高效的多媒体文件处理。方案通过事件驱动机制,将文件处理任务解耦,并自动弹性扩展,按需付费,简化部署流程,提高处理效率。本文还提供了详细的部署步骤与体验反馈,展示了从配置到文件处理的全过程。
|
3月前
|
存储 监控 Serverless
函数计算发布功能问题之用户在使用主流函数计算产品的日志服务时可能会遇到使用成本的问题如何解决
函数计算发布功能问题之用户在使用主流函数计算产品的日志服务时可能会遇到使用成本的问题如何解决
|
3月前
|
弹性计算 关系型数据库 Serverless
函数计算驱动多媒体文件处理:高效、稳定与成本优化实践
本次测评的解决方案《告别资源瓶颈,函数计算驱动多媒体文件处理》展示了如何利用阿里云函数计算高效处理多媒体文件。文档结构清晰、内容详实,适合新客户参考。方案提供了一键部署与手动部署两种方式,前者简便快捷,后者灵活性高但步骤较多。通过部署,用户可体验到基于函数计算的文件处理服务,显著提升处理效率和系统稳定性。此外,测评还对比了应用内处理文件与函数计算处理文件的不同,突出了函数计算在资源管理和成本控制方面的优势。
22716 19
|
3月前
|
编解码 弹性计算 Serverless
解锁多媒体处理新纪元:阿里云函数计算,一键驱动高效、灵活、成本优化的文件处理解决方案!
【8月更文挑战第2天】随着云计算的发展,高效灵活的多媒体处理成为必需。阿里云函数计算提供全托管服务,用户仅需上传代码,平台自动配置资源,支持毫秒级弹性伸缩。与对象存储服务集成,实现视频转码、音频提取及图片压缩等功能,按需付费降低成本。示例展示了基于Python的视频转码函数,体现其在多媒体处理领域的强大潜力和优势。
50 10
|
3月前
|
编解码 运维 Serverless
函数计算驱动多媒体文件处理:告别资源瓶颈,释放处理能力
随着多媒体内容的爆炸性增长,如何高效地处理和管理多媒体文件成为了各大企业面临的重大挑战。阿里云提供的函数计算(Function Compute)驱动多媒体文件处理解决方案,为这一问题提供了高效、灵活的解决途径。本文将对该解决方案进行详细评测,分析其优势和应用场景。
59 1
|
4月前
|
存储 缓存 监控
通用研发提效问题之动态调整干预能力,如何解决
通用研发提效问题之动态调整干预能力,如何解决
|
4月前
|
存储 SQL 运维
MSSQL性能调优精要:索引深度优化、查询高效重构与并发精细控制
在MSSQL数据库的运维与优化领域,性能调优是一项复杂而细致的工作,直接关系到数据库的稳定性和响应速度
|
11月前
|
设计模式 存储 缓存
怎么在有限的时间和资源里,设计出一个既经济高效又能保持扩展性的架构呢?
怎么在有限的时间和资源里,设计出一个既经济高效又能保持扩展性的架构呢?
51 1