當 relationship 本身就是問題的一部分,而不只是 entity 之間的裝飾箭頭時,graph database 才真正有價值。
Property graph 的基本組件
Neo4j property graph model 使用:
- Node 表示 entity;
- Label 表示 node role;
- 有方向、有型別的 relationship;
- Node 與 relationship 都可以有 property。
(:Researcher)-[:AUTHORED {position: 1}]->(:Paper)
(:Paper)-[:CITES]->(:Paper)
(:Paper)-[:ABOUT]->(:Topic)
Author position 放在 AUTHORED 上,因為它描述的是 connection,不是任一 endpoint。
白話來說: Relational junction row 和 graph relationship 常常代表同一個 fact;graph model 只是把沿著這些 fact 走訪,變成最主要的操作。
從選擇性高的起點開始
先用 identity constraint 幫 planner 找到起始 node:
CREATE CONSTRAINT researcher_id IF NOT EXISTS
FOR (r:Researcher)
REQUIRE r.id IS UNIQUE;
再從明確 anchor traversal:
MATCH (r:Researcher {id: $researcherId})-[:AUTHORED]->(p:Paper)
-[:ABOUT]->(t:Topic)
RETURN t.name, count(*) AS papers
ORDER BY papers DESC;
Index 幫忙找到 r,不代表後面無上限的 traversal 會自動便宜。後續成本取決於 relationship degree、path length 與 filter。
Variable-length path 一定要有邊界
MATCH path = (a:Researcher {id: $id})-[:COAUTHORED_WITH*1..3]-(other)
WHERE other <> a
RETURN DISTINCT other.id, length(path)
LIMIT 100;
1..3、relationship type、selective start 與 result limit 都在限制工作量。Dense graph 上的 unbounded pattern 可能產生組合爆炸。
白話來說: 「朋友的朋友」聽起來很小,但每人有數百個鄰居時會立刻膨脹。Path length 不只是產品需求,也是效能參數。
讀懂 plan
Read query 前加 PROFILE 會實際執行並收集 runtime statistics;EXPLAIN 則只產生 plan。
優先檢查:
- 起點是否使用
NodeIndexSeek,而不是掃描整個 label; Expand後 row 數是否意外放大;- Filter 是否等大量 traversal 完成後才執行;
- Pattern 太寬造成的重複 expansion。
不要一開始就強制 index hint。先修正 identity constraint、predicate 與 statistics,再考慮 override planner。
什麼時候不需要 graph
Workload 主要是 tabular report、穩定 join、aggregate 與強 multi-row constraint 時,relational model 通常比較合適。資料通常以 bounded aggregate 一起讀取時,document model 可能更自然。Variable relationship path 和 neighborhood question 才是 graph 的強項。
複習卡
- Relationship attribute 放在 relationship。
- Constraint 與 index 用來找到 selective starting node。
- Traversal cost 取決於 degree 與 path length。
- Variable-length path 要刻意設限。
EXPLAIN看 plan;PROFILE實際執行並量測。- 因為問題是 graph-shaped 才選 graph,不是因為 diagram 有很多箭頭。