為什麼要使用圖形資料庫,或者更具體地說是Neo4j作為我們資料庫選擇?
人們在邏輯上通常很自然使用類似圖的結構來模擬或描述它們的特定問題域。權限控制就是一個例子。在許多企業應用程式中。您通常擁有用戶表,角色表和資源表。然後你會使用多對多關係表來將用戶映射到對應的角色和角色資源。最後你至少有五個關係表代表一個相當的簡單的數據結構,實際上只是一個簡單的圖形。
選擇正確的數據存儲,可以使你的應用程式像雄鷹展翅一般自由翱翔。使用圖模型將數據存儲為圖形,結構由頂點和邊組成,用於對任何圖形建模的場景都會非常合適。
為了進行比較,我們將使用傳統關係數據庫和neo4j,探索一個社交網絡的例子。這裡我們使用一組互為朋友的用戶數據。如下圖所示是用戶之間的社交網絡。
社交網絡關係
注意邊的方向包含了語義信息,友誼關係應該是雙向的。在Neo 4j中,雙向關係使用兩條相反方向的邊進行建模。這裡為簡單起見,我們將朋友關係建模為單方向的直接關係。
關係型資料庫中的圖數據
在關係數據庫中,通常有兩個用於存儲社交關係的表格:一個表格保存用戶信息,另一個用於保存用戶的朋友關係
用戶表和用戶朋友關係表
以下為使用mysql資料庫創建表的SQL腳本
-- 用戶表定義
create table t_user (
id bigint not null
name varchar(255)not null
primary key (id)
);
-- 用戶朋友關係表定義
create table t_user_friend (
id bigint not null
user 1 bigint not null
user 2 bigint not null
primary key (id)
);
-- 外鍵約束
alter table t_user_friend
add index FK416055ABC6132571(user_1),
add constraint FK416055ABC6132
foreign key (user_1) references t_user (id);
alter table t_user_friend
add index FK416055ABC6132572 (user_2)
add constraint FK416055ABC6132572
foreign key (user_2) references t_user (id);
如何查詢關係數據?如果想查找某個用戶的所有朋友,可能會做以下的查詢語句:
select count(distinct uf.*) from t_user_friend uf where uf_user_1=?
如何找到用戶朋友的所有朋友?這次你通常會將t_user_friend表格和自己join在一起來進行查詢
select count (distinct uf2.*)from t_user_friend ufl
inner join t_user_friend uf2 on ufl.user_2 = uf2.user_1
where ufl.user_1 =?
社交網絡通常有一個功能,就是向你推薦你的社交網絡里達到多層深度的潛在的朋友或聯繫人。如果你想做類似的事情,找到朋友的朋友的朋友,你需要再加另一個join操作
select count(distinct uf3. *)from t_user_friend ufl
inner join t_user_friend uf2 on uf1.user_2 = uf2.user_1
inner join t_user_friend uf3 on uf2.user_2 = uf3.user_1
where ufl.user_1 = ?
同樣,要疊代四層的友誼關係,你需要四個join操作。如果要的到著名的六度理論里的所有關係,將需要六個join。
使用這種方法是在正常不過的,但確有一個潛在的問題。雖然你只對單個用戶所有的朋友的朋友感興趣,但你必須對t_user_friend全表的所有數據進行join操作,然後丟棄那些你沒有興趣的數據行。這不是一個大問題,但如果你的社交網絡變得越來越大,你可能會遇到嚴重的性能問題。
這種操作這會給你的關係數據庫引擎帶來巨大壓力。為了說明此類查詢的性能,這裡針對1,000個用戶的小數據集,運行了幾次friends-of-friends查詢,但每次增加搜索的深度,並記錄結果。
在1000用戶的小數據集上,表t_user_friend包含1,000條記錄用戶,每個用戶平均有50個朋友,包含1000 x 50 = 50000條記錄,表t_user包含1000條記錄。
在每個深度,我們首先運行查詢10次,從而預熱緩存幫助提高表現。記錄每個深度的最快執行時間。下表顯示了實驗結果:
查詢時間
如你所見,MySQL可以非常好地處理深度為2和3的查詢。join操作在關係型資料庫世界中很常見,大多數資料庫都是如此設計,在某些特定列上使用索引相關也能幫助最大化join操作的性能。
然而,當深度達到4和5時,您會看到性能顯著下降:一個涉及4個join的查詢需要10秒以上才能完成,而在深度為5時更花了太長時間,超過一分半鐘,雖然計數結果沒有改變。這恰恰說明了在對圖結構數據建模時MysQL的局限性:深度圖遍歷需要多個join操作,關係數據庫通常並不擅長這種處理。
通過圖遍歷進行關係數據查詢
關係數據庫對於多對多關係建模並不是那麼合適。而反過來,Neo4j則擅長多對多關係的處理,讓我們來看看它如何使用相同的數據集,但是把用戶建模為節點,而不是表,列或者外鍵,然後將朋友關係建模為圖中的邊。
圖遍歷是通過在互相連接的兩個節點之間移動來訪問圖中的一組節點的操作。這是圖資料庫中進行數據檢索的基本操作。遍歷的一個關鍵概念是這個操作僅僅是局部相關的, 遍歷查詢時只需要考慮所需的數據,無需像關係型資料庫的join一樣,在整個數據集上執行代價極高的分組操作。
Neo4j提供了豐富的用於遍歷的APl,你可以使用它們進行圖搜索。此外,也可以使用restful API或Neo4j查詢語言來遍曆數據。在neo4j中,要獲取某個用戶所有朋友的所有朋友,可以運行下面的java代碼:
// 指定遍歷的要求描述
TraversalDescription traversalDescription = Traversal.description()
.relationships ("IS_FRIEND_OF", Direction.OUTGOING)
.evaluator(Evaluators. atDepth(2))
.uniqueness (Uniqueness.NODE_GLOBAL)
// 執行遍歷
Iterablenodes = traversalDescription.traverse(nodeById).nodes()
遍歷開始之前,你選擇將要開始遍歷的節點。然後你遵循所有的友誼關係(箭頭),記錄所有訪問過的節點作為結果。當我們的遍歷規則失效時,遍歷停止。例如,規則可以是僅從起始節點訪問深度為1的節點,在這種情況下,一旦完成全部深度1的節點的訪問,遍歷停止。
下表顯示了在圖資料庫上運行遍歷的性能指標。用到的數據與之前MysQl資料庫中的相同。同樣,這是針對1000個用戶的數據集,每位用戶平均有50位朋友的用戶。
Neo4j遍歷查詢時間
可以看見,除了最簡單的查詢,Neo4j在其他查詢的性能表現上都是明顯更好的那一個。只有在尋找朋友的朋友時(深度為2),mysql性能可與Neo4j遍歷的性能相媲美。在深度為3時的遍歷比mysql快4倍。在深度為4,結果則要好五個數量級。深度為5時,Neo4j結果的速度甚至要比mysql要快1000萬倍。 mysQl查詢性能下降如此之快正是由於,join操作需要對全部數據進行笛卡爾積運算,其中大部分的數據我們並不需要。