Skip to content
DEE
Database Engineering Essentials

[DEE-404] 圖形資料庫建模

INFO

當關係是主要的查詢目標時使用圖形資料庫。圖形模型擅長可變深度的遍歷和關係密集的查詢,這些在關聯式資料庫中需要複雜的遞迴 join。

背景

圖形資料庫(如 Neo4j、Amazon Neptune 和 TigerGraph)將資料儲存為節點(實體)和關係(實體之間的邊),兩者都可以攜帶屬性。不同於關聯式資料庫中關係是隱含的(以外鍵表示,透過 join 解析),圖形資料庫將關係視為一等公民,擁有自己的識別、方向、類型和屬性。

圖形資料庫的根本優勢是免索引鄰接:每個節點直接參考其鄰居,因此遍歷一個關係是常數時間的指標查找,不受圖形總大小影響。在關聯式資料庫中,找到「朋友的朋友的朋友」需要在一個可能龐大的表格上執行三次自我 join。在圖形資料庫中,這是一個三跳遍歷,以毫秒級執行。

Cypher 是 Neo4j 的宣告式查詢語言,使用 ASCII 藝術語法來表達圖形模式:(a)-[r:KNOWS]->(b) 可以自然地讀作「節點 a 有一個 KNOWS 關係指向節點 b」。這種視覺化語法使複雜的遍歷出奇地易讀。

圖形資料庫不是關聯式或文件資料庫的通用替代品。它們是為關係密集的資料而專門打造的,其中連接的深度和方向是主要的查詢關注點。

原則

  • SHOULD在主要查詢涉及遍歷可變或未知深度的關係時使用圖形資料庫(例如最短路徑、推薦、詐欺偵測、存取控制)。
  • MUST將節點建模為領域實體,將關係建模為它們之間有意義的連接。關係SHOULD有類型(動詞)且MAY攜帶屬性。
  • SHOULDMATCH 起點中使用的節點屬性建立索引。沒有索引的話,Cypher 查詢會退回到完整的標籤掃描。
  • MUST NOT將圖形資料庫當作文件存儲使用 —— 在節點屬性中塞入大型 JSON 物件會違背圖形模型的初衷。
  • SHOULD NOT在簡單的 CRUD 操作中選擇圖形資料庫,因為關係是淺層的(一層外鍵)。關聯式或文件資料庫更適合這類工作負載。

視覺化

範例

社群網路:朋友的朋友

找出 Alice 的朋友認識但 Alice 尚未認識的人(潛在好友推薦):

cypher
// 找出尚未成為 Alice 好友的朋友的朋友
MATCH (alice:Person {name: 'Alice'})-[:FRIENDS_WITH]->(friend)-[:FRIENDS_WITH]->(foaf)
WHERE foaf <> alice
  AND NOT (alice)-[:FRIENDS_WITH]->(foaf)
RETURN foaf.name AS recommended, COUNT(friend) AS mutual_friends
ORDER BY mutual_friends DESC
LIMIT 10;

在 SQL 中,這需要在 friendships 表格上進行多次自我 join:

sql
-- 關聯式等價寫法(PostgreSQL)—— 更難閱讀和擴展
SELECT p.name, COUNT(DISTINCT mf.id) AS mutual_friends
FROM friendships f1
JOIN friendships f2 ON f1.friend_id = f2.person_id
JOIN persons p ON f2.friend_id = p.id
JOIN persons mf ON f1.friend_id = mf.id
WHERE f1.person_id = (SELECT id FROM persons WHERE name = 'Alice')
  AND f2.friend_id <> f1.person_id
  AND f2.friend_id NOT IN (
    SELECT friend_id FROM friendships
    WHERE person_id = f1.person_id
  )
GROUP BY p.name
ORDER BY mutual_friends DESC
LIMIT 10;

推薦引擎:協同過濾

找出購買了與 Alice 相同產品的人也購買的其他產品:

cypher
// 「購買了 X 的人也購買了 Y」推薦
MATCH (alice:Person {name: 'Alice'})-[:PURCHASED]->(product)<-[:PURCHASED]-(other)
      -[:PURCHASED]->(rec)
WHERE NOT (alice)-[:PURCHASED]->(rec)
  AND rec <> product
RETURN rec.name AS recommended_product,
       COUNT(DISTINCT other) AS recommenders
ORDER BY recommenders DESC
LIMIT 5;

存取控制:權限遍歷

判斷使用者是否透過群組成員資格和角色繼承擁有資源的存取權限:

cypher
// 檢查使用者是否對資源有 READ 權限(任何路徑)
MATCH path = (user:User {email: 'alice@example.com'})
             -[:MEMBER_OF*1..3]->(group:Group)
             -[:HAS_ROLE]->(role:Role)
             -[:GRANTS]->(perm:Permission {action: 'READ'})
             -[:ON]->(resource:Resource {name: 'secret-doc'})
RETURN path LIMIT 1;

這個單一查詢遍歷使用者 -> 群組(最多 3 層巢狀)-> 角色 -> 權限 -> 資源。在 SQL 中,這需要遞迴 CTE 和多次 join。

圖形優於關聯式的場景

場景圖形優勢關聯式限制
可變深度遍歷(N 跳的朋友的朋友)透過免索引鄰接每跳成本固定每增加一跳就多一次自我 join;成本呈指數增長
帶有關係屬性的多對多關係是一等公民,擁有自己的屬性和類型中介表格變得複雜;關係上的屬性需要額外欄位或表格
最短路徑 / 路徑搜尋內建 shortestPath() 演算法需要遞迴 CTE 或應用層 BFS,難以最佳化
靈活結構的連接新的關係類型無需結構遷移新的關係類型需要新的中介表格和外鍵約束
跨多種實體類型的模式匹配MATCH (a)-[*1..5]->(b) 遍歷最多 5 跳的任何路徑多表 join 搭配 union,難以泛化

常見錯誤

錯誤為何有害修正方式
用圖形資料庫做簡單 CRUD —— 基本的實體建立、檢索和更新,沒有遍歷查詢圖形資料庫增加了複雜性(不同的查詢語言、不同的運維工具),在查詢不遍歷關係時沒有任何好處。對簡單 CRUD 使用關聯式或文件資料庫。僅在遍歷查詢是主要使用情境時採用圖形資料庫。
未對節點屬性建立索引 —— MATCH 起點使用的屬性沒有索引沒有屬性索引的話,MATCH (p:Person {name: 'Alice'}) 會觸發所有 Person 節點的完整掃描。對頻繁查詢的屬性建立索引:CREATE INDEX FOR (p:Person) ON (p.name)
當作文件存儲使用 —— 在節點屬性中儲存大型 JSON 物件大型屬性值違背了免索引鄰接的優勢,增加記憶體佔用。圖形變成了索引不良的文件存儲。保持節點屬性小而純量。將大型內容存放在文件資料庫中,從圖形中參考它。
所有東西都建模為節點 —— 某些資料應該是屬性過多的節點建立導致簡單屬性查找時不必要的遍歷。不是所有東西都值得成為節點。如果資料不參與關係且僅作為其父項的一部分被存取,就將它設為屬性。
忽略關係方向 —— 在方向有意義時建立無方向的關係Cypher 在建立時要求指定方向。如果你隨意選擇方向,查詢會變得混亂且可能回傳錯誤結果。根據領域語義建模方向(例如 (a)-[:REPORTS_TO]->(b) 表示 a 向 b 報告)。對於對稱關係,選擇一個慣例並在查詢中處理雙向。

相關 DEE

參考資料