組織架構新型資料結構思考

zxdposter發表於2022-01-28

1 前言

許可權架構一直是企業中,中小型系統繞不開的功能,這篇文章中,涉及到的篇幅僅限於不是特別複雜的中小型系統。

以我遇到的為例,許可權架構一般分為組織管理、角色管理、使用者管理。

組織管理,掌管的是資料許可權,一個組織的橫向之間相互隔離,縱向之間確定能見度,

角色管理,掌管的是功能許可權,不同的角色有不同的選單,有不同的功能。

使用者管理,是組織與角色的共同體現。

在這裡,重點要說的是組織管理,因為在不同的系統中,角色本身代表的含義差別很大,角色可能有上下級管理,也可能沒有,或許是能看到的選單不同,或許是能看到的按鈕不同,這些功能的他體現還要根據前端架構的不同進行設計,可以說沒有什麼準確的設計,能夠涵蓋角色功能代表的意義。

但是組織一般共性較多一些,組織與前端關係不是很深,一般只需要一個管理功能,使用者能夠掛靠到組織下即可,主要還是看後端如何處理不同組織關係下的資料查詢。

2 組織架構資料結構一般形式

在一般系統中,大部分設計組織架構,都是使用一張表,利用 pid 作為關聯本表 id 的外來鍵,使用 pid 與 id 的層層遞進的關聯關係。

大概類似於下面的表格

idnamepid
0頂層組織null
1二層組織0
2三層組織1

這樣我們通過 pid 為 null 來確定這是頂層機構,通過 pid 為 1 的記錄找到二層組織的子記錄,如果想找到三層組織,還需要運用複雜的 sql,來重新組織資料關係,來方便找到一連串的組織關係。

例如這段在 oracle 中的 sql,這樣的 sql 可以用來找到頂層組織所有的關聯關係。

SELECT * FROM table_organ
            START WITH id = '0'
            CONNECT BY NOCYCLE PRIOR ID = pid

這樣做問題不大,如果說只侷限於組織關係表這一張表的查詢,那麼我們的問題永遠也不用上升層次。

但是針對於關聯表的查詢,這樣的方式就顯得有些笨重了。如果你要查詢,頂層機構及其隸屬機構在某一張表的所屬資料,就需要用到下面的 sql:

select * from some_table where organId in (SELECT id FROM table_organ
            START WITH id = '1'
            CONNECT BY NOCYCLE PRIOR ID = pid)

這樣的實現沒有任何問題,目前的資料庫肯定已經考慮到了在條件不變的情況下,子查詢只會查詢一次,也就是說,這樣的查詢,副作用只是會多帶來一次查詢。

但是在經歷了多次這樣的功能設計之後,舊的情況已經漸漸不能滿足於我了。

3 組織架構資料結構的新形式

表現組織層級的不一定要使用縱向的資料關係,也可以使用橫向的資料關係。

什麼是縱向、橫向?下面來解釋一下。

3.1 縱向資料關係表

縱向資料關係表示如下表,可以看到,表示層級關係的高低,是通過增加層級的深度的來表示的。

idpid
0null
10
21

3.2 橫向資料關係表

那麼使用橫向資料關係表示呢?如下表。在下面表中,表示層級關係是通過橫向擴充套件資料的長度來表示的,層級關係的高低,取決於橫向資料的長短。(order_seq 表示自己在當前層級下的順序,為的是方便資料處理,tier 為資料的層級,是否需要取決於實際場景能否用到,例如當希望能夠查詢指定深度的資料時)

idauthorityorder_seqtier
00/00
10/1/11
20/1/0/02

3.3 資料結構的設計要點

3.3.1 查詢方式

在利用了橫向設計的資料之後,查詢就變得異乎尋常的簡單。

當我希望查詢頂層組織的所有關聯關係時:

SELECT * FROM table_organ where authority like '0/%'

當我希望查詢所屬頂層組織的資料時:

select * from some_table where authority like '0/%'

當我希望查詢通過中間一層組織,找到所有上下級組織的資料時:

select * from some_table where authority like '0/1/%' or instr('0/1/', authority) = 1

3.3.2 分隔符

在上面的 3.2 中的橫向資料關係表中,你能夠看到我使用的是分割是 / ,並且資料也要以 / 分隔符作為結尾。

這是為了防止在下一層資料過多時,導致的資料判斷異常,例如 0/10/10,在上面的 sql 中,若沒有最後一位 / 的阻隔,查詢結果就會出現資料異常。

3.3.3 數字的選擇

在這種設計中,數字的選擇的正確與否至關重要,它是整個資料結構的基礎。

需要準守下面兩個要點:

  1. 有相同上級的同級中不允許出現重複數字,有不同上級的同級可以出現重複數字,例如 0/0/1/0/
  2. 儘量不讓數字只增大,而是能夠填補之前的空缺,例如你有 0/1/3/ 這三個頂層組織,當 1/ 被刪除之後,再次新增需要能夠填補 1 的空缺

由此,設計出一個 sql,來幫助我們實現這個需求,sql 為 mysql 的版本。

select *
from (select *
      from (select c.rownum
            from (SELECT @rownum := @rownum + 1 as rownum, a.order_seq
                  from (select *
                        from cem_organ
                        where authority regexp '^[0-9]+/$') a,
                       (SELECT @rownum := -1) b
                  ORDER BY a.order_seq) c
            where c.rownum != c.order_seq
            limit 1) as e
      UNION
      select *
      from (
               select max(d.order_seq) as num
               from cem_organ d
               where d.authority regexp '^[0-9]+/$') as f
      where f.num is not null) as g
limit 1

這個 sql 分為三部分。

  1. 第一部分是為了找到缺失的數字,思路是利用 order_seqrow_num 的不相等記錄。
  2. 第二部分是為了找到最大的數字。
  3. 最後合併第一部分與第二部分的結果,只取前一位,若此 sql 沒有返回任何記錄,那麼程式取 0。

使用這個 sql 的同時,也要注意到利用 authority 通過正則,來限制你的上級,若是頂層組織,值為 ^[0-9]+/$,若是非頂層組織,值為 ^0/[0-9]+/$,以此類推 ^0/1/[0-9]+/$。(也可以通過 like 的形式做到同樣的效果)

在這裡還需要注意一個關鍵的問題,那就是併發產生的問題,如果同一時間執行此條 sql,會造成取值相同的情況,因此需要在執行 sql 的地方加上分散式鎖,來確保併發情況下能夠得到唯一的值。鎖的值使用父級 authority 來提高一些效能。

3.4 優點

3.4.1 查詢邏輯簡單容易移植

這個優點是顯而易見的,當你想找到組織關係中的所有的下級關係,查詢邏輯只需要用一個 like,並且 like 基本上所有的資料庫語法都是一樣的,因為簡單,所以容易移植。

regex 查詢同樣也是如此。

3.4.2 查詢速度快

在擁有百萬資料級別時,特別是查詢的條件為擁有許多隸屬下級的頂層組織時,優點尤為明顯。

另外,在有兩層或者兩層以上的上下級關係之間查詢,有著無可比擬的優勢。

例如,當組織下分管著部門,部門下又分管著印表機,當你把相同的思路應用於部門表以及印表機表時,你完全可以跳過組織、部門,直接查詢。

select * from table_printer where authority like '0/%'

在這裡,你可以聯想一下,如果使用 pid 關聯的方式,該如何查詢,特別是在一個大的組織關係中,當部門的資料量過千時,怎麼破解資料庫對 in 數量的限制,也是需要考慮的地方。

3.4.1 資料更容易理解

相比於之前的 pid 連線方式,authority 更加能夠直觀的體現資料之間的關係。

3.5 缺點

3.5.1 實現邏輯複雜

由前面的文章可以看出,為了實現這種基於資料結構的上下級關係,我們需要滿足諸多條件,需要在各個地方小心翼翼的維護 authority 代表上下級關係的字串,一旦出錯,帶來的影響將是巨大的。

3.5.2 不適合特別複雜的邏輯

目前我還沒有遇到可能會超越這種資料結構的需求,但是難免客戶會從一些刁鑽的角度提出另類的需求,而這種資料結構其實從根本上來說是十分精巧脆弱的,一旦出現了變動,很可能會增加實現邏輯的複雜度,甚至於推倒重來。

因此需要小心的權衡這種資料所帶來的利弊。

最後

這種新型的資料結構,是經過錘鍊與驗證的,確實從實際上幫助你解決一些業務痛點。

但它同樣也是一把雙刃劍,能夠完整的駕馭它,需要較強的邏輯思維與程式碼設計能力。

否則最後的程式碼結構,會看起來到處是為了實現這種邏輯打上的補丁,因此不斷的重構、精簡程式碼、完善的迴歸測試,也是十分必要的。

相關文章