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

目录

一、出现的问题

部门树接口超时

二、问题分析

源代码分析

三、解决方案

具体实现思路

四、优化的效果


一、出现的问题

部门树接口超时

无论是在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 dept
        SET 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。其中还对部门重新进行了排序,并且带有其他逻辑。效果堪称显著。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pycharm 中的 Mark Directory As 的作用是什么?

文章目录 Mark Directory As 的作用PYTHONPATH 是什么PYTHONPATH 作用注意事项 Mark Directory As 的作用 可以查看官网&#xff1a;https://www.jetbrains.com/help/pycharm/project-structure-dialog.html#-9p9rve_3 我们这里以 Mark Directory As Sources 为例进行介绍。 这…

机器学习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],…

二叉树——429,515,116

今天继续做关于二叉树层序遍历的相关题目&#xff0c;一共有三道题&#xff0c;思路都借鉴于最基础的二叉树的层序遍历。 LeetCode429.N叉树的层序遍历 这道题不再是二叉树了&#xff0c;变成了N叉树&#xff0c;也就是该树每一个节点的子节点数量不确定&#xff0c;可能为2&a…

WPF进阶 | WPF 样式与模板:打造个性化用户界面的利器

WPF进阶 | WPF 样式与模板&#xff1a;打造个性化用户界面的利器 一、前言二、WPF 样式基础2.1 什么是样式2.2 样式的定义2.3 样式的应用 三、WPF 模板基础3.1 什么是模板3.2 控件模板3.3 数据模板 四、样式与模板的高级应用4.1 样式继承4.2 模板绑定4.3 资源字典 五、实际应用…

六百六十六,盐豆不带盐了

我还有救吗...... P11040 #include <iostream> #include <vector> #include <cstring> using namespace std; const int MOD 998244353; const int MAXN 1e7 5; const int MAXM 1e7 5; int n, q; int m; int dpTable[MAXN][32]; int prefixSum[MAXN][32…

传输层协议 UDP 与 TCP

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; 前置复盘&#x1f98b; 传输层&#x1f98b; 再谈端口号&#x1f98b; 端口号范围划分&#x1f98b; 认识知名端口号 (Well-Know Port Number) 二&#xf…

LabVIEW无人机航线控制系统

介绍了一种无人机航线控制系统&#xff0c;该系统利用LabVIEW软件与MPU6050九轴传感器相结合&#xff0c;实现无人机飞行高度、速度、俯仰角和滚动角的实时监控。系统通过虚拟仪器技术&#xff0c;有效实现了数据的采集、处理及回放&#xff0c;极大提高了无人机航线的控制精度…

最新功能发布!AllData数据中台核心菜单汇总

🔥🔥 AllData大数据产品是可定义数据中台,以数据平台为底座,以数据中台为桥梁,以机器学习平台为中层框架,以大模型应用为上游产品,提供全链路数字化解决方案。 ✨奥零数据科技官网:http://www.aolingdata.com ✨AllData开源项目:https://github.com/alldatacenter/…

【memgpt】letta 课程1/2:从头实现一个自我编辑、记忆和多步骤推理的代理

llms-as-operating-systems-agent-memory llms-as-operating-systems-agent-memory内存 操作系统的内存管理

HarmonyOS:给您的应用添加通知

一、通知介绍 通知旨在让用户以合适的方式及时获得有用的新消息&#xff0c;帮助用户高效地处理任务。应用可以通过通知接口发送通知消息&#xff0c;用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应用&#xff0c;通知主要有以下使用场景&#xff1a; 显示…

leetcode——二叉树的最近公共祖先(java)

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是它自己的…

【FreeRTOS 教程 六】二进制信号量与计数信号量

目录 一、FreeRTOS 二进制信号量&#xff1a; &#xff08;1&#xff09;二进制信号量作用&#xff1a; &#xff08;2&#xff09;二进制信号量与互斥锁的区别&#xff1a; &#xff08;3&#xff09;信号量阻塞时间&#xff1a; &#xff08;4&#xff09;信号量的获取与…

python学opencv|读取图像(五十五)使用cv2.medianBlur()函数实现图像像素中值滤波处理

【1】引言 在前述学习过程中&#xff0c;已经探索了取平均值的形式进行图像滤波处理。 均值滤波的具体的执行对象是一个nXn的像素核&#xff0c;对这个像素核内所有像素点的BGR值取平均值&#xff0c;然后把这个平均的BGR值直接赋给像素核中心位置的核心像素点&#xff0c;由…

OpenAI 正式推出Deep Research

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

多模态论文笔记——NaViT

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细解读多模态论文NaViT&#xff08;Native Resolution ViT&#xff09;&#xff0c;将来自不同图像的多个patches打包成一个单一序列——称为Patch n’ Pack—…

VLAN 基础 | 不同 VLAN 间通信实验

注&#xff1a;本文为 “ Vlan 间通信” 相关文章合辑。 英文引文&#xff0c;机翻未校。 图片清晰度限于原文图源状态。 未整理去重。 How to Establish Communications between VLANs? 如何在 VLAN 之间建立通信&#xff1f; Posted on November 20, 2015 by RouterSwi…

使用Pygame制作“吃豆人”游戏

本篇博客展示如何使用 Python Pygame 编写一个简易版的“吃豆人&#xff08;Pac-Man&#xff09;” 风格游戏。这里我们暂且命名为 Py-Man。玩家需要控制主角在一个网格地图里移动、吃掉散布在各处的豆子&#xff0c;并躲避在地图中巡逻的幽灵。此示例可帮助你理解网格地图、角…

springboot使用rabbitmq

使用springboot创建rabbitMQ的链接。 整个项目结构如下&#xff1a; 1.maven依赖 <dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>3.4.1</version> </dependency>application.y…

安卓(android)订餐菜单【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可查看源码&#xff09; 1.掌握Activity生命周的每个方法。 2.掌握Activity的创建、配置、启动和关闭。 3.掌握Intent和IntentFilter的使用。 4.掌握Activity之间的跳转方式、任务栈和四种启动模式。 5.掌握在Activity中添加…

RabbitMQ快速上手及入门

概念 概念&#xff1a; publisher&#xff1a;生产者&#xff0c;也就是发送消息的一方 consumer&#xff1a;消费者&#xff0c;也就是消费消息的一方 queue&#xff1a;队列&#xff0c;存储消息。生产者投递的消息会暂存在消息队列中&#xff0c;等待消费者处理 exchang…