在圖數(shù)據(jù)庫(kù)里,有時(shí)候會(huì)碰到一個(gè)看起來(lái)要做出一些取舍的地方鄙煤,那就是:很多元素都有的晾匠,且重復(fù)值很多的一個(gè)屬性,是應(yīng)該將其作為頂點(diǎn)的屬性來(lái)存儲(chǔ)梯刚,還是當(dāng)做一個(gè)頂點(diǎn)凉馆,然后用邊來(lái)將元素指向該頂點(diǎn)來(lái)表示一個(gè)屬性?例如以下的例子:
在我們的圖中需要存儲(chǔ)用戶(hù)注冊(cè)時(shí)的phone段和ip段。這兩個(gè)屬性是所有的用戶(hù)頂點(diǎn)都需要的屬性澜共,而且這兩個(gè)屬性的重復(fù)度比較高向叉,也就是很多很多頂點(diǎn)都有相同的phone段或者ip段。此時(shí)我們有兩種不同的思路來(lái)解決這個(gè)問(wèn)題:
思路1:將phone和ip當(dāng)做用戶(hù)頂點(diǎn)的屬性嗦董。根據(jù)我們需要的讀取模式母谎,給phone和ip創(chuàng)建索引來(lái)加快查詢(xún)效率。
思路2:phone和ip不作為屬性京革,而是將不同的phone和ip段當(dāng)做頂點(diǎn)保存在圖數(shù)據(jù)庫(kù)中奇唤,用戶(hù)的ip和phone的值通過(guò)邊指向不同的代表phone的頂點(diǎn)和ip的頂點(diǎn),通過(guò)這種方式來(lái)實(shí)現(xiàn)保存其屬性匹摇。
表面看起來(lái)這兩種方式都可以實(shí)現(xiàn)功能咬扇,實(shí)際上他們之間還是有很多不同的。我們需要分析其各自不同的利弊廊勃,從而在不同的情形下選用不同的模型來(lái)解決問(wèn)題懈贺。
用屬性存儲(chǔ)的方式的優(yōu)點(diǎn):
這種思路更直觀(guān)一些,也更符合我們一般對(duì)頂點(diǎn)和屬性的理解坡垫。
對(duì)與頂點(diǎn)屬性本身的查詢(xún)效率更高一些梭灿,因?yàn)椴簧婕绊旤c(diǎn)的traversal。如果使用頂點(diǎn)的方式存儲(chǔ)葛虐,則涉及到邊的向外遍歷胎源。尤其是如果我們要一次獲取多個(gè)屬性時(shí),這種方式直接在頂點(diǎn)內(nèi)就可以獲取屿脐,而第二種方式則需要遍歷n條邊來(lái)獲取n個(gè)屬性涕蚤。
存儲(chǔ)需要的空間小一些,不需要在storage backend中使用額外的記錄來(lái)存儲(chǔ)這些保存屬性的點(diǎn)以及指向這些代表屬性的點(diǎn)的邊的诵。
圖的schema會(huì)簡(jiǎn)單一些万栅。不需要額外定義多個(gè)vertex label和edge label。
在導(dǎo)入頂點(diǎn)時(shí)速度會(huì)快一些西疤,尤其是在開(kāi)啟了bulk loading模式時(shí)烦粒。因?yàn)檫@種方式在添加一個(gè)用戶(hù)頂點(diǎn)時(shí),只需要添加一個(gè)頂點(diǎn)和其對(duì)應(yīng)的屬性即可代赁。而第二種方式里扰她,我們除了需要添加用戶(hù)頂點(diǎn)本身,還要在添加用戶(hù)頂點(diǎn)之前先把屬性頂點(diǎn)添加到庫(kù)里(而在數(shù)據(jù)準(zhǔn)備階段芭碍,還需要將這些屬性的數(shù)據(jù)單獨(dú)剝離出來(lái)徒役,并去掉其重復(fù)值),并在添加用戶(hù)頂點(diǎn)時(shí)從庫(kù)中查詢(xún)已經(jīng)添加的屬性頂點(diǎn)窖壕,并添加指向這些頂點(diǎn)的邊忧勿。這會(huì)導(dǎo)致圖的準(zhǔn)備階段多浪費(fèi)大量的時(shí)間杉女。
用頂點(diǎn)存儲(chǔ)的方式的優(yōu)點(diǎn):
以上的屬性存儲(chǔ)的方式的優(yōu)點(diǎn)也恰恰是頂點(diǎn)存儲(chǔ)的方式的缺點(diǎn)。這種方式并不直觀(guān)鸳吸,對(duì)頂點(diǎn)屬性值的查詢(xún)效率也不高熏挎,而且會(huì)增加額外的記錄來(lái)保存點(diǎn)和邊。另外會(huì)導(dǎo)致schema變得復(fù)雜晌砾,也會(huì)在導(dǎo)入數(shù)據(jù)時(shí)額外浪費(fèi)時(shí)間花在屬性頂點(diǎn)的查詢(xún)上坎拐。
但這種方式也有其優(yōu)點(diǎn),那就是將屬性的由點(diǎn)及面變得簡(jiǎn)單贡羔,尤其是在一個(gè)分析中涉及多個(gè)由點(diǎn)及面的情形時(shí)廉白,這種方式的優(yōu)點(diǎn)就能展現(xiàn)出來(lái)。而這也常常是圖數(shù)據(jù)庫(kù)需要做的東西乖寒,尤其在涉及社群分析時(shí)。
我們這里的由點(diǎn)及面代表的含義是:我們需要找出所有的跟某個(gè)或者某幾個(gè)頂點(diǎn)的某些屬性相同的全部頂點(diǎn)院溺。
以以下的要求為例:當(dāng)我們需要找到跟張三和李四使用同一個(gè)電話(huà)段或者ip段的用戶(hù)使用的phone和ip的分組情況楣嘁。
如果我們的圖的schema是基于第一種情況存儲(chǔ)的,這就變成了一個(gè)比較棘手的問(wèn)題珍逸。我們需要首先找到張三和李四的電話(huà)作為phoneSet逐虚,然后找到張三和李四的ip作為ipSet,然后查詢(xún)電話(huà)和ip與集合中的值相同的記錄谆膳。
這種需求叭爱,如果放在普通的數(shù)據(jù)庫(kù)中,實(shí)際上就是根據(jù)屬性的join操作漱病。這種看似簡(jiǎn)單的操作在圖數(shù)據(jù)庫(kù)中要實(shí)現(xiàn)確面臨比較大的困難买雾,原因在于:圖數(shù)據(jù)庫(kù)中解決連接的操作是通過(guò)邊來(lái)實(shí)現(xiàn)的,且沒(méi)有別的解決方式杨帽。換句話(huà)說(shuō)漓穿,圖數(shù)據(jù)庫(kù)中,相同屬性之間的點(diǎn)是沒(méi)有東西能夠?qū)⑵淇焖龠B接起來(lái)的注盈。
這也就是第二種方式最大的意義所在晃危。以該題為例,我們通過(guò)gremlin可以很容易的實(shí)現(xiàn)(假設(shè)張三的頂點(diǎn)是v3老客,李四頂點(diǎn)是v4):
g.V(v3, v4).out("ip", "phone").in("ip", "phone").values().groupCount()
總結(jié):如果屬性重復(fù)度很低僚饭,或者屬性不涉及連接操作,則使用屬性存儲(chǔ)是更好也更直觀(guān)的方式胧砰。但如果涉及屬性的連接操作鳍鸵,而且屬性的重復(fù)度很高,則可以考慮用點(diǎn)來(lái)存儲(chǔ)屬性朴则。