巧妙利用數據結構優化部門查詢

目錄

一、出現的問題

部門樹接口超時

二、問題分析

源代碼分析

三、解決方案

具體實現思路

四、優化的效果


一、出現的問題

部門樹接口超時

無論是在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.pswp.cn/news/894573.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/894573.shtml
英文地址,請注明出處:http://en.pswp.cn/news/894573.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

QT簡單實現驗證碼(字符)

0&#xff09; 運行結果 1&#xff09; 生成隨機字符串 Qt主要通過QRandomGenerator類來生成隨機數。在此之前的版本中&#xff0c;qrand()函數也常被使用&#xff0c;但從Qt 5.10起&#xff0c;推薦使用更現代化的QRandomGenerator類。 在頭文件添加void generateRandomNumb…

JavaFX - 3D 形狀

在前面的章節中&#xff0c;我們已經了解了如何在 JavaFX 應用程序中的 XY 平面上繪制 2D 形狀。除了這些 2D 形狀之外&#xff0c;我們還可以使用 JavaFX 繪制其他幾個 3D 形狀。 通常&#xff0c;3D 形狀是可以在 XYZ 平面上繪制的幾何圖形。它們由兩個或多個維度定義&#…

深入理解開放尋址法中的三種探測序列

一、引言 開放尋址法是解決散列表中沖突的一種重要方法&#xff0c;當發生沖突&#xff08;即兩個不同的鍵通過散列函數計算得到相同的散列值&#xff09;時&#xff0c;它會在散列表中尋找下一個可用的存儲位置。而探測序列就是用于確定在發生沖突后&#xff0c;依次嘗試哪些…

【雙指針題目】

雙指針 美麗區間&#xff08;滑動窗口&#xff09;合并數列&#xff08;雙指針的應用&#xff09;等腰三角形全部所有的子序列 美麗區間&#xff08;滑動窗口&#xff09; 美麗區間 滑動窗口模板&#xff1a; int left 0, right 0;while (right < nums.size()) {// 增大…

為什么命令“echo -e “\033[9;0]“ > /dev/tty0“能控制開發板上的LCD不熄屏?

為什么命令"echo -e “\033[9;0]” > /dev/tty0"能控制開發板上的LCD不熄屏&#xff1f; 在回答這個問題前請先閱讀我之前寫的與tty和終端有關的博文 https://blog.csdn.net/wenhao_ir/article/details/145431655 然后再來看這條命令的解釋就要容易些了。 這條…

嵌入式八股文面試題(一)C語言部分

1. 變量/函數的聲明和定義的區別&#xff1f; &#xff08;1&#xff09;變量 定義不僅告知編譯器變量的類型和名字&#xff0c;還會分配內存空間。 int x 10; // 定義并初始化x int x; //同樣是定義 聲明只是告訴編譯器變量的名字和類型&#xff0c;但并不為它分配內存空間…

go-zero學習筆記(三)

利用goctl生成rpc服務 編寫proto文件 // 聲明 proto 使用的語法版本 syntax "proto3";// proto 包名 package demoRpc;// golang 包名(可選) option go_package "./demo";// 如需為 .proto 文件添加注釋&#xff0c;請使用 C/C 樣式的 // 和 /* ... */…

Javascript代碼庫-jQuery入門

摘自千鋒教育kerwin的js教程 jQuery 是一個前端庫&#xff0c;也是一個方法庫他里面封裝著一些列的方法供我們使用我們常用的一些方法它里面都有&#xff0c;我們可以直接拿來使用就行了jQuery 之所以好用&#xff0c;很多人愿意使用&#xff0c;是因為他的幾個優點太強大了 優…

【25考研】南開軟件考研復試復習重點!

一、復試內容 復試采取現場復試的方式。復試分為筆試、機試和面試三部分。三部分合計100分&#xff0c;其中筆試成績占30%、機試成績占30%、面試成績占40%。 1.筆試&#xff1a;專業綜合基礎測試 考核方式&#xff1a;閉卷考試&#xff0c;時長為90分鐘。 筆試考查內容范圍…

【最長上升子序列Ⅱ——樹狀數組,二分+DP,純DP】

題目 代碼&#xff08;只給出樹狀數組的&#xff09; #include <bits/stdc.h> using namespace std; const int N 1e510; int n, m; int a[N], b[N], f[N], tr[N]; //f[i]表示以a[i]為尾的LIS的最大長度 void init() {sort(b1, bn1);m unique(b1, bn1) - b - 1;for(in…

012-51單片機CLD1602顯示萬年歷+鬧鐘+農歷+整點報時

1. 硬件設計 硬件是我自己設計的一個通用的51單片機開發平臺&#xff0c;可以根據需要自行焊接模塊&#xff0c;這是用立創EDA畫的一個雙層PCB板&#xff0c;所以模塊都是插針式&#xff0c;不是表貼的。電路原理圖在文末的鏈接里&#xff0c;PCB圖暫時不選擇開源。 B站上傳的…

容器迭代器iterator

文章目錄 1、自定義String實現iterator2、自定義vector實現iterator3、迭代器失效問題 迭代器的功能&#xff1a;提供一種統一的方式&#xff0c;來透明的遍歷容器。 迭代器可以透明的訪問容器內部的元素的值&#xff0c;而無需了解其底層遍歷機制具體是數組的下標還是鏈表的指…

對象的實例化、內存布局與訪問定位

一、創建對象的方式 二、創建對象的步驟: 一、判斷對象對應的類是否加載、鏈接、初始化: 虛擬機遇到一條new指令&#xff0c;首先去檢查這個指令的參數能否在Metaspace的常量池中定位到一個類的符號引用&#xff0c;并且檢查這個符號引用代表的類是否已經被加載、解析和初始化…

傳輸層協議 UDP 與 TCP

&#x1f308; 個人主頁&#xff1a;Zfox_ &#x1f525; 系列專欄&#xff1a;Linux 目錄 一&#xff1a;&#x1f525; 前置復盤&#x1f98b; 傳輸層&#x1f98b; 再談端口號&#x1f98b; 端口號范圍劃分&#x1f98b; 認識知名端口號 (Well-Know Port Number) 二&#xf…

實驗十一 Servlet(二)

實驗十一 Servlet(二) 【實驗目的】 1&#xff0e;了解Servlet運行原理 2&#xff0e;掌握Servlet實現方式 【實驗內容】 改造實驗10&#xff0c;引入數據庫&#xff0c;創建用戶表&#xff0c;包括用戶名和密碼&#xff1a;客戶端通過login.jsp發出登錄請求&#xff0c;請求…

服務SDK三方新版中央倉庫和私服發布詳解

預備信息Github倉庫發布Gradle版本匹配Gradle項目構建全局變量定義Gradle項目Nexus倉庫配置與發布過程Gradle項目發布至Sonatype中央倉庫配置過程總結當我們在實現一個項目技術總結、工具類封裝或SDK封裝,通常是為了方便開發者使用特定服務或平臺而提供的一組工具和API。您可能…

git 新項目

新項目git 新建的項目如何進行git 配置git git config --global user.name "cc" git config --global user.email ccexample.com配置遠程倉庫路徑 // 添加 git remote add origin http://gogs/cc/mc.git //如果配錯了&#xff0c;刪除 git remote remove origin初…

openmv的端口被拆分為兩個 導致電腦無法訪問openmv文件系統解決辦法 openmv USB功能改動 openmv驅動被更改如何修復

我之前誤打誤撞遇到一次&#xff0c;直接把openmv的全部端口刪除卸載然后重新插上就會自動重新裝上一個openmv端口修復成功&#xff0c;大家可以先試試不行再用下面的方法 全部卸載再重新插拔openmv 要解決OpenMV IDE中出現的兩個端口問題&#xff0c;可以嘗試以下步驟&#x…

利用Python高效處理大規模詞匯數據

在本篇博客中&#xff0c;我們將探討如何使用Python及其強大的庫來處理和分析大規模的詞匯數據。我們將介紹如何從多個.pkl文件中讀取數據&#xff0c;并應用一系列算法來篩選和擴展一個核心詞匯列表。這個過程涉及到使用Pandas、Polars以及tqdm等庫來實現高效的數據處理。 引…

LabVIEW雙光子成像系統:自主創新,精準成像,賦能科研

雙光子成像系統&#xff1a;自主創新&#xff0c;精準成像&#xff0c;賦能科研 第一部分&#xff1a;概述 雙光子成像利用兩個低能量光子同時激發熒光分子&#xff0c;具有深層穿透、高分辨率、低光損傷等優勢。它能實現活體深層組織的成像&#xff0c;支持實時動態觀察&…