巧妙利用数据结构优化部门查询

目录

一、出现的问题

部门树接口超时

二、问题分析

源代码分析

三、解决方案

具体实现思路

四、优化的效果


一、出现的问题

部门树接口超时

无论是在A项目还是在B项目中,都存在类似的页面,其实就是一个部门列表或者叫组织列表。

从页面的展示形式上来看它是一个树状结构。因此在最早的接口设计上也是通过一个接口返回该租户的部门页面进行显示。

接口的返回形式如上。其实也很简单。就是一个通过一个嵌套的对象实现了树

但是在我们私有化的过程中有一些大型的集团客户,他们整个集团的部门数量上万个甚至可能出现 10w 多个部门。因此出现了接口超时20s 都没有结果的情况出现了。

数据权限查询子部门超时

其实,不光有这个场景,在我们的数据权限设计上有一个,这个意思呢就是。如果用户的数据权限为本级及子级的,那这个用户就能查看用户所属部门和该部门子部门下产生的数据。按照目前的实现方式,那我们就要知道这个用户的部门和子部门。

那这个查询子部门的过程也会同样出现超时的情况。

二、问题分析

源代码分析

走读原先的部门树接口逻辑发现,是通过递归查询的方式实现。

在数据表的设计上,是通过一个 parent_id 字段关联的是父部门的 id。因此可以通过该字段查询到这个部门的子部门的列表。然后通过递归不断地遍历就能查询出所有的部门并且组装成树。

伪代码逻辑其实就是

// 递归查询函数,参数为要查找的父部门id
List<Department> findDepartmentsByParentId(int parentId) {
    List<Department> departmentList = queryFromMysql(parentId);
  // 用于存放查询结果的列表
    List<Department> result = new List<Department>();  
    for (Department department : departmentList) {
        if (department.parentId == parentId) {
            result.add(department);
            // 递归调用,查找当前部门下的子部门,并将结果合并
            List<Department> subDepartments = findDepartmentsByParentId(department.id);
            result.addAll(subDepartments);
        }
    }

    return result;

 可能出现问题的原因

  1. 子部门数量太多。导致queryFromMysql 这个方法执行的次数多
  2. 部门层级深。导致queryFromMysql 这个方法执行的次数多

核心原因其实还是每一次查询子部门都需要通过queryFromMysql这个方法执行一次,类似如下的

select * from department where tenant_id=xxx and parent_id=xxx;

不难发现,问题的根本原因就是 sql 查询执行次数太多了。那么现在问题的核心就是减少 sql 查询的次数。就能解决问题。

当然这里没有考虑改交互的方式,比如一次只查询一级,慢慢展开呢,其实一样,数据权限的子部门那里保持原样还是会超时。

另外,细心的同学可能发现这个代码有问题呀,为什么要从 root 一层一层地查下去,直接用租户 id 查询出所有的部门数据然后再组装成树不是会解决吗。确实可以解决部门树的查询,但是数据权限的子部门那里保持原样还是会超时。

因此要解决的问题核心还是传入任何一个部门 id 能够快速查询出子部门列表

三、解决方案

  1. 加缓存,可能存的数据比较多吧,每一个部门 id 都需要存一份子部门的数据。初始化构建缓存的时候查询依旧慢,这里不讨论了。因为核心问题是解决查询数据库慢的问题
  2. 减少查询数据库的次数,从而减少响应时间

具体实现思路

回顾标题,要用数据结构去优化查询,那可能就是在原先表的基础增加一些辅助字段来提高查询的效率。加索引肯定没啥大作用了,因为这里的查询慢是因为次数多而不是因为单词查询数据量大导致。当然索引该加还是得加。

 

我们可以添加为每个节点添加两个值,代表数据的范围(leftValut, rightValue)

需要满足以下要求:

    1. 每个节点的 leftValut < rightValue
    2. 子节点的 leftValut > 父节点的 leftValue
    3. 子节点的 rightValue < 父节点的 rightValue

我们按照,这个规则给上面的结构赋值下:

再观察下,怎么去查子部门呢?

比如  2-1(2,9) 的 子部门为  3-1(3,6) 和  3-2(7,8)以及 3-1 的子部门 4-1(4,5)。 可以看出 3,4,5,6,7,8 都在 2,9 之间。

因此可以用,2< leftValue/rightValue <9  查询到 3-1,3-2,4-1 .而这几个正好是 2-1 的子部门。

根据我们赋值的限制条件不难看出一个部门的所有子部门的 leftValue 和 rightValue 是在父部门的范围内的。所以我们在查询一个部门的所有子部门时,就可以用如下的伪代码去查询

//1. 查询该部门的 leftValue 和 rightValue
Dept dept = queryDeptFromMysql(parentId);
int leftValue = dept.getLeftValue();
int rightValue = dept.getRightValue();

//2. 通过leftValue 和 rightValue 查询子部门
List<Dept> childDepts = queryChildDeptsFromMysql(leftValue,rightValue)

//sql 的话就是这样
// select * from dept where tenant_id=xxx and left_value > #{leftValue} and left_value < #{rightValue}
  
//3. 根据实际情况返回列表,或者组装成树

在我们有这两个左右值的情况下,一条 sql 就能查询出所有符合条件的结果。那么现在的问题来到了如何去为每个部门节点进行赋值。

如何对部门进行赋值

可以分为以下几种情况讨论:(这里只考虑增加修改的情况都是单部门)

​​​​​​​初始化   初始化是最简单的,我们只需要以根节点(leftValue 值为 1)为开始深度优先遍历, 每次+1 即可。

这里的初始值设置为 1 即可。很简单

​​​​​​​新增部门

 

可以看出变化的部分是

    1. 新增了一个部门 4-2 他的值分别是 4-1 的 (rightValue +1,rightValue+2) 即 6,7
    2. 因为 3-1 增加了子部门所以他的值范围必定发生变化,变化的增长值就是 2 ( 也是增加的节点个数*2)因为部门 4-2 已经是 6,7 了 所以 3-1 部门会发生变化为 3,8
    3. 同理后续右边(比发生变化的值大的)节点的值都会发生对应的变化

其实不难看出,每个值大于等于 4-1 的rightValue +1,(即新增加的4-2的 leftValue) 的值都增加了 2 ,无论他是leftValue还是rightValue。

那么新增加一个子节点可以这样去更新。当然你还要把新加的部门加进去

  UPDATE deptSET left_value = IF(left_value > #{leftValue}, left_value + #{changeValue}, left_value),right_value = IF(right_value > #{leftValue}, right_value +  #{changeValue}, right_value)where tenant_id= #{tenantId};

 

changeValue 是什么呢。其实就是变化的节点的个数*2,而这里就是 2,因为只新增了一个子节点 4-2

那如果,新增加的部门不是一个,而是多个呢?

其实这种情况在我们这种页面上不会出现。因为添加部门的时候只会添加一个。

那删除和修改就是一个道理了~

就不多演示了

​​​​​​​感兴趣的可以评论区讨论~

四、优化的效果

部门树(约有 1.8w 个部门)的接口返回了如此巨大数据的情况的下只用了 400ms。其中还对部门重新进行了排序,并且带有其他逻辑。效果堪称显著。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/12125.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据分析】案例04:豆瓣电影Top250的数据分析与Web网页可视化(numpy+pandas+matplotlib+flask)

豆瓣电影Top250的数据分析与Web网页可视化(numpy+pandas+matplotlib+flask) 豆瓣电影Top250官网:https://movie.douban.com/top250写在前面 实验目的:实现豆瓣电影Top250详情的数据分析与Web网页可视化。电脑系统:Windows使用软件:PyCharm、NavicatPython版本:Python 3.…

【线程】基于环形队列的生产者消费者模型

1 环形队列 环形队列采用数组来模拟&#xff0c;用取模运算来模拟环状特性。 1.如何判断环形队列为空或者为满? 当环形队列为空时&#xff0c;头和尾都指向同一个位置。当环形队列为满时&#xff0c;头和尾也都指向同一个位置。 因此&#xff0c; 可以通过加计数器或者标记…

Vue指令v-html

目录 一、Vue中的v-html指令是什么&#xff1f;二、v-html指令与v-text指令的区别&#xff1f; 一、Vue中的v-html指令是什么&#xff1f; v-html指令的作用是&#xff1a;设置元素的innerHTML&#xff0c;内容中有html结构会被解析为标签。 二、v-html指令与v-text指令的区别…

OPENGLPG第九版学习 - 着色器基础

文章目录 2.1 着色器与OpenGL2.2 0penGL的可编程管线2.3 OpenGL着色语言GLSL概述2.3.1 使用GLSL构建着色器变量的声明变量的作用域变量的初始化构造函数 、 类型转换聚合类型访问向量和矩阵中的元素结构体数组多维数组 2.3.2 存储限制符const 存储限制符in 存储限制符out 存储限…

路径规划之启发式算法之二十九:鸽群算法(Pigeon-inspired Optimization, PIO)

鸽群算法(Pigeon-inspired Optimization, PIO)是一种基于自然界中鸽子群体行为的智能优化算法,由Duan等人于2014年提出。该算法模拟了鸽子在飞行过程中利用地标、太阳和磁场等导航机制的行为,具有简单、高效和易于实现的特点,适用于解决连续优化问题。 更多的仿生群体算法…

Docker Compose的使用

文章首发于我的博客&#xff1a;https://blog.liuzijian.com/post/docker-compose.html 目录 Docker Compose是什么Docker Compose安装Docker Compose文件Docker Compose常用命令案例&#xff1a;部署WordPress博客系统 Docker Compose是什么 Docker Compose是Docker官方的开源…

AP单类平均准确率

P_true N_true P_pred TP Fp N_pred FN TNP NTP&#xff08;真正样本&#xff0c;与真实框IoU大于阈值的框&#xff09; FP&#xff08;假正样本&#xff0c;与真实框IoU小于阈值的框&#xff09; TN&#xff08;真负样本&#xff0c;背景&#xff09;…

Leetcode—1427. 字符串的左右移【简单】Plus

2025每日刷题&#xff08;206&#xff09; Leetcode—1427. 字符串的左右移 实现代码 class Solution { public:string stringShift(string s, vector<vector<int>>& shift) {// shift[i] [dir, amount]// dir 0(左) or 1(右)// 左表示正, 右表示负int len…

机器学习10

自定义数据集 使用scikit-learn中svm的包实现svm分类 代码 import numpy as np import matplotlib.pyplot as pltclass1_points np.array([[1.9, 1.2],[1.5, 2.1],[1.9, 0.5],[1.5, 0.9],[0.9, 1.2],[1.1, 1.7],[1.4, 1.1]])class2_points np.array([[3.2, 3.2],[3.7, 2.9],…

股票入门知识

股票入门&#xff08;更适合中国宝宝体制&#xff09; 股市基础知识 本文介绍了股票的基础知识&#xff0c;股票的分类&#xff0c;各板块发行上市条件&#xff0c;股票代码&#xff0c;交易时间&#xff0c;交易规则&#xff0c;炒股术语&#xff0c;影响股价的因素&#xf…

Golang 并发机制-3:通道(channels)机制详解

并发编程是一种创建性能优化且响应迅速的软件的强大方法。Golang&#xff08;也称为 Go&#xff09;通过通道&#xff08;channels&#xff09;这一特性&#xff0c;能够可靠且优雅地实现并发通信。本文将揭示通道的概念&#xff0c;解释其在并发编程中的作用&#xff0c;并提供…

C#,入门教程(11)——枚举(Enum)的基础知识和高级应用

上一篇&#xff1a; C#&#xff0c;入门教程(10)——常量、变量与命名规则的基础知识https://blog.csdn.net/beijinghorn/article/details/123913570 不会枚举&#xff0c;就不会编程&#xff01; 枚举 一个有组织的常量系列 比如&#xff1a;一个星期每一天的名字&#xf…

读书笔记--分布式架构的异步化和缓存技术原理及应用场景

本篇是在上一篇的基础上&#xff0c;主要对分布式应用架构下的异步化机制和缓存技术进行学习&#xff0c;主要记录和思考如下&#xff0c;供大家学习参考。大家知道原来传统的单一WAR应用中&#xff0c;由于所有数据都在同一个数据库中&#xff0c;因此事务问题一般借助数据库事…

【C++】继承(下)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解C的继承&#xff08;下&#xff09;&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 5.继承与友元6.继承与静态成员7.复杂的菱形继承及菱形虚拟继承8.继…

基于LLM的路由在专家混合应用:一种新颖的交易框架,该框架在夏普比率和总回报方面提升了超过25%

“LLM-Based Routing in Mixture of Experts: A Novel Framework for Trading” 论文地址&#xff1a;https://arxiv.org/pdf/2501.09636 摘要 随着深度学习和大语言模型&#xff08;LLMs&#xff09;的不断进步&#xff0c;混合专家&#xff08;MoE&#xff09;机制在股票投资…

Med-R2:基于循证医学的检索推理框架:提升大语言模型医疗问答能力的新方法

Med-R2 : Crafting Trustworthy LLM Physicians through Retrieval and Reasoning of Evidence-Based Medicine Med-R2框架Why - 这个研究要解决什么现实问题What - 核心发现或论点是什么How - 1. 前人研究的局限性How - 2. 你的创新方法/视角How - 3. 关键数据支持How - 4. 可…

【Blazor学习笔记】.NET Blazor学习笔记

我是大标题 我学习Blazor的顺序是基于Blazor University&#xff0c;然后实际内容不完全基于它&#xff0c;因为它的例子还是基于.NET Core 3.1做的&#xff0c;距离现在很遥远了。 截至本文撰写的时间&#xff0c;2025年&#xff0c;最新的.NET是.NET9了都&#xff0c;可能1…

C++ Primer 迭代器

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

2 [GitHub遭遇严重供应链投毒攻击]

近日&#xff0c;有黑客针对 Discord Top.gg 的GitHub 账户发起了供应链攻击&#xff0c;此次攻击导致账户密码、凭证和其他敏感信息被盗&#xff0c;同时也影响到了大量开发人员。 Checkmarx 在一份技术报告中提到&#xff0c;黑客在这次攻击中使用了多种TTP&#xff0c;其中…

【AudioClassificationModelZoo-Pytorch】基于Pytorch的声音事件检测分类系统

源码&#xff1a;https://github.com/Shybert-AI/AudioClassificationModelZoo-Pytorch 模型测试表 模型网络结构batch_sizeFLOPs(G)Params(M)特征提取方式数据集类别数量模型验证集性能EcapaTdnn1280.486.1melUrbanSound8K10accuracy0.974, precision0.972 recall0.967, F1-s…