新聞中心
圖數(shù)據(jù)庫是一種新型的數(shù)據(jù)庫技術,它以圖的方式存儲和管理數(shù)據(jù),可以有效地解決傳統(tǒng)數(shù)據(jù)庫無法解決的一些復雜數(shù)據(jù)問題,被廣泛應用于社交網(wǎng)絡、智能推薦、物聯(lián)網(wǎng)等領域。本文將深入,以期讓讀者更好地了解這項技術。

專注于為中小企業(yè)提供網(wǎng)站制作、做網(wǎng)站服務,電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)饒陽免費做網(wǎng)站提供優(yōu)質(zhì)的服務。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了千余家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。
一、圖數(shù)據(jù)庫的概念及發(fā)展歷程
圖數(shù)據(jù)庫是指將數(shù)據(jù)以圖的形式進行存儲和管理的數(shù)據(jù)庫,它是一種基于圖論的新型數(shù)據(jù)庫技術。圖數(shù)據(jù)庫的發(fā)展可以追溯到二十世紀初,當時,人們開始使用圖來描述網(wǎng)絡和電路的關系,這些圖被看作是一種數(shù)據(jù)結(jié)構(gòu)。隨著互聯(lián)網(wǎng)的發(fā)展,人們發(fā)現(xiàn)圖還可以用來描述復雜網(wǎng)絡中的關系,例如社交網(wǎng)絡、推薦系統(tǒng)等。因此,圖數(shù)據(jù)庫開始嶄露頭角,成為了數(shù)據(jù)庫領域的一股新力量。
二、圖數(shù)據(jù)庫的基本功能
1. 存儲和管理圖形數(shù)據(jù)
圖數(shù)據(jù)庫最基本的功能就是存儲和管理圖形數(shù)據(jù),這是圖數(shù)據(jù)庫和傳統(tǒng)關系型數(shù)據(jù)庫的更大區(qū)別。在圖數(shù)據(jù)庫中,數(shù)據(jù)是以節(jié)點和邊的形式保存的,節(jié)點代表實體,邊代表節(jié)點之間的關系。這種數(shù)據(jù)結(jié)構(gòu)更符合實際應用場景,也更容易理解和維護。
2. 支持復雜數(shù)據(jù)查詢和分析
傳統(tǒng)的關系型數(shù)據(jù)庫只能進行簡單的數(shù)據(jù)查詢和分析,而圖數(shù)據(jù)庫則可以進行復雜的數(shù)據(jù)查詢和分析。例如,我們可以使用圖數(shù)據(jù)庫來查找和分析人與人之間的關系,或查找某個節(jié)點的所有鄰居節(jié)點。這些操作對于傳統(tǒng)數(shù)據(jù)庫而言非常困難,但對于圖數(shù)據(jù)庫來說卻是非常簡單的。
3. 支持高效的數(shù)據(jù)處理和存儲
圖數(shù)據(jù)庫使用圖結(jié)構(gòu)來存儲數(shù)據(jù),這種數(shù)據(jù)結(jié)構(gòu)非常高效。它可以幫助我們快速地處理和存儲大量的數(shù)據(jù),同時還可以提高數(shù)據(jù)的處理速度和效率。這對于大型應用系統(tǒng)來說尤為重要,因為它們需要處理和存儲大量的數(shù)據(jù)。
三、圖數(shù)據(jù)庫的優(yōu)勢
1. 可以處理復雜數(shù)據(jù)
傳統(tǒng)的關系型數(shù)據(jù)庫只能處理結(jié)構(gòu)化的數(shù)據(jù),而圖數(shù)據(jù)庫可以處理非結(jié)構(gòu)化和半結(jié)構(gòu)化數(shù)據(jù)。這種數(shù)據(jù)結(jié)構(gòu)更符合現(xiàn)代應用的需求,也更容易理解和維護。例如,我們可以使用圖數(shù)據(jù)庫來處理社交網(wǎng)絡中的朋友和關注關系,或處理物聯(lián)網(wǎng)中的傳感器和控制器之間的關系。
2. 可以進行深度分析和推理
圖數(shù)據(jù)庫可以進行深度分析和推理,幫助我們更好地理解和掌握數(shù)據(jù)。例如,我們可以使用圖數(shù)據(jù)庫來分析人與人之間的關系,進而推斷出某個人的興趣偏好或職業(yè)方向。這種分析對于推薦系統(tǒng)、人際關系管理等應用領域非常有幫助。
3. 可以提高處理效率和性能
圖數(shù)據(jù)庫使用圖數(shù)據(jù)結(jié)構(gòu)來存儲和管理數(shù)據(jù),這種數(shù)據(jù)結(jié)構(gòu)非常高效。它可以幫助我們快速地處理數(shù)據(jù),同時還能提高處理效率和性能。這對于大型應用系統(tǒng)和高并發(fā)系統(tǒng)來說非常重要,因為它可以提高系統(tǒng)的響應速度和穩(wěn)定性。
四、圖數(shù)據(jù)庫的應用場景
1. 社交網(wǎng)絡和人際關系管理
圖數(shù)據(jù)庫可以用于處理人與人之間的關系,例如在社交網(wǎng)絡、人際關系管理系統(tǒng)等應用場景中。它可以幫助我們更好地理解和管理人際關系,提高人際交往的效率和質(zhì)量。
2. 智能推薦系統(tǒng)
圖數(shù)據(jù)庫可以用于處理用戶的興趣偏好和行為數(shù)據(jù),從而提供更準確的推薦服務。例如在電商領域中,我們可以使用圖數(shù)據(jù)庫來處理用戶的購買歷史、瀏覽記錄等數(shù)據(jù),進而推薦更符合用戶興趣的商品。
3. 物聯(lián)網(wǎng)
圖數(shù)據(jù)庫可以用于處理物聯(lián)網(wǎng)中的傳感器和控制器之間的關系,例如在智能家居、智能城市等應用領域中。它可以幫助我們更好地理解和管理物聯(lián)網(wǎng)設備,提高智能化程度和效率。
五、結(jié)論
圖數(shù)據(jù)庫是一種非常有前途的新型數(shù)據(jù)庫技術,它以圖的方式管理和處理數(shù)據(jù),可以應用于多個領域,例如社交網(wǎng)絡、智能推薦、物聯(lián)網(wǎng)等。通過使用圖數(shù)據(jù)庫,我們可以更好地理解和管理數(shù)據(jù),提高應用系統(tǒng)的效率和性能。未來,圖數(shù)據(jù)庫將會得到更廣泛的應用和發(fā)展。
相關問題拓展閱讀:
- 如何用 Python 實現(xiàn)一個圖數(shù)據(jù)庫(Graph Database)?
- 圖數(shù)據(jù)庫和關系數(shù)據(jù)庫的區(qū)別
如何用 Python 實現(xiàn)一個圖數(shù)據(jù)庫(Graph Database)?
本文章是 重寫 500 Lines or Less 系列的其中一篇,目標是重寫 500 Lines or Less 系列的原有項目:Dagoba: an in-memory graph database。
Dagoba 是作者設計用來展示如何從零開始自己實現(xiàn)一個圖數(shù)據(jù)庫( Graph Database )。該名字似乎來源于作者喜歡的一個樂隊,另一個原因是它的前綴 DAG 也正好是有向無環(huán)圖 ( Directed Acyclic Graph ) 的縮寫。本文也沿用了該名稱。
圖是一種常見的數(shù)據(jù)結(jié)構(gòu),它將信息描述為若干獨立的節(jié)點( vertex ,為了和下文的邊更加對稱,本文中稱為 node ),以及把節(jié)點關聯(lián)起來的邊( edge )。我們熟悉的鏈表以及多種樹結(jié)構(gòu)可以看作是符合特定規(guī)則的圖。圖在路徑選擇、推薦算法以及神經(jīng)網(wǎng)絡等方面都是重要的核心數(shù)據(jù)結(jié)構(gòu)。
既然圖的用途如此廣泛,一個重要的問題就是如何存儲它。如果在傳統(tǒng)的關系數(shù)據(jù)庫中存儲圖,很自然的做法就是為節(jié)點和邊各自創(chuàng)建一張表,并用外鍵把它們關聯(lián)起來。這樣的話,要查找某人所有的子女,就可以寫下類似下面的查詢:
還好,不算太復雜。但是如果要查找孫輩呢?那恐怕就要使用子查詢或者 CTE(Common Table Expression) 等特殊構(gòu)造了。再往下想,曾孫輩又該怎么查詢?孫媳婦呢?
這樣我們會意識到,SQL 作為查詢語言,它只是對二維數(shù)據(jù)表這種結(jié)構(gòu)而設計的,用它去查詢圖的話非常笨拙,很快會變得極其復雜,也難以擴展。針對圖而言,我們希望有一種更為自然和直觀的查詢語法,類似這樣:
為了高效地存儲和查詢圖這種數(shù)據(jù)結(jié)構(gòu),圖數(shù)據(jù)庫( Graph Database )應運而生。因為和傳統(tǒng)的關系型數(shù)據(jù)庫存在極大的差異,所以它屬于新型數(shù)據(jù)庫也就是 NoSql 的一個分支(其他分支包括文檔數(shù)據(jù)庫、列數(shù)據(jù)庫等)。圖數(shù)據(jù)庫的主要代表包括 Neo4J 等。本文介紹的 Dagoba 則是具備圖數(shù)據(jù)庫核心功能、主要用于教學和演示的一個簡單的圖數(shù)據(jù)庫。
原文代碼是使用 JavaScript 編寫的,在定義調(diào)用接口時大量使用了原型( prototype )這種特有的語言構(gòu)造。對于其他主流語言的用戶來說,原型的用法多少顯得有些別扭和不自然。
考慮到本系列其他數(shù)據(jù)庫示例大多是用 Python 實現(xiàn)的,本文也按照傳統(tǒng),用 Python 重寫了原文的代碼。同樣延續(xù)之前的慣例,為了讓讀者更好地理解程序是如何逐步完善的,我們用迭代式的方法完成程序的各個組成部分。
原文在 500lines 系列的 Github 倉庫中只包含了實現(xiàn)代碼,并未包含測試。按照代碼注釋說明,測試程序位于作者的另一個代碼庫中,不過和 500lines 版本的實現(xiàn)似乎略有不同。
本文實現(xiàn)的代碼參考了原作者的測試內(nèi)容,但跳過了北歐神話這個例子——我承認確實不熟悉這些神祇之間的親緣關系,相信中文背景的讀者們多數(shù)也未必了解,雖然作者很喜歡這個例子,想了想還是不要徒增困惑吧。因此本文在編寫測試用例時只參考了原文關于家族親屬的例子,放棄了神話相關的部分,盡管會減少一些趣味性,相信對于入門級的代碼來說這樣也夠用了。
本文實現(xiàn)程序位于代碼庫的 dagoba 目雀中錄下。按照本系列程序的同意規(guī)則,要想直接執(zhí)行各個已完成的步驟,讀者可以在根目錄下的 main.py 找到相應的代碼位置,取消注釋并運行即可。
本程序的所有步驟只需要 Python3 ,測試則使用內(nèi)置的 unittest , 不需要額外的第三方庫。原則上 Python3.6 以亂歲仿上版本應該都可運行,但我只在 Python3.8.3 環(huán)境下完整測試過。
本文實現(xiàn)的程序從最簡單的案嘩纖例開始,通過每個步驟逐步擴展,最終形成一個完整的程序。這些步驟包括:
接下來依次介紹各個步驟。
回想一下,圖數(shù)據(jù)庫就是一些點( node )和邊( edge )的?,F(xiàn)在我們要做出的一個重大決策是如何對節(jié)點/邊進行建模。對于邊來說,必須指定它的關聯(lián)關系,也就是從哪個節(jié)點指向哪個節(jié)點。大多數(shù)情況下邊是有方向的——父子關系不指明方向可是要亂套的!
考慮到擴展性及通用性問題,我們可以把數(shù)據(jù)保存為字典( dict ),這樣可以方便地添加用戶需要的任何數(shù)據(jù)。某些數(shù)據(jù)是為數(shù)據(jù)庫內(nèi)部管理而保留的,為了明確區(qū)分,可以這樣約定:以下劃線開頭的特殊字段由數(shù)據(jù)庫內(nèi)部維護,類似于私有成員,用戶不應該自己去修改它們。這也是 Python 社區(qū)普遍遵循的約定。
此外,節(jié)點和邊存在互相引用的關系。目前我們知道邊會引用到兩端的節(jié)點,后面還會看到,為了提高效率,節(jié)點也會引用到邊。如果僅僅在內(nèi)存中維護它們的關系,那么使用指針訪問是很直觀的,但數(shù)據(jù)庫必須考慮到序列化到磁盤的問題,這時指針就不再好用了。
為此,更好按照數(shù)據(jù)庫的一般要求,為每個節(jié)點維護一個主鍵( _id ),用主鍵來描述它們之間的關聯(lián)關系。
我們之一步要把數(shù)據(jù)庫的模型建立起來。為了測試目的,我們使用一個最簡單的數(shù)據(jù)庫模型,它只包含兩個節(jié)點和一條邊,如下所示:
按照 TDD 的原則,首先編寫測試:
與原文一樣,我們把數(shù)據(jù)庫管理接口命名為 Dagoba 。目前,能夠想到的最簡單的測試是確認節(jié)點和邊是否已經(jīng)添加到數(shù)據(jù)庫中:
assert_item 是一個輔助方法,用于檢查字典是否包含預期的字段。相信大家都能想到該如何實現(xiàn),這里就不再列出了,讀者可參考 Github 上的完整源碼。
現(xiàn)在,測試是失敗的。用最簡單的辦法實現(xiàn)數(shù)據(jù)庫:
需要注意的是,不管添加節(jié)點還是查詢,程序都使用了拷貝后的數(shù)據(jù)副本,而不是直接使用原始數(shù)據(jù)。為什么要這樣做?因為字典是可變的,用戶可以在任何時候修改其中的內(nèi)容,如果數(shù)據(jù)庫不知道數(shù)據(jù)已經(jīng)變化,就很容易發(fā)生難以追蹤的一致性問題,最糟糕的情況下會使得數(shù)據(jù)內(nèi)容徹底混亂。
拷貝數(shù)據(jù)可以避免上述問題,代價則是需要占用更多內(nèi)存和處理時間。對于數(shù)據(jù)庫來說,通常查詢次數(shù)要遠遠多于修改,所以這個代價是可以接受的。
現(xiàn)在測試應該正常通過了。為了讓它更加完善,我們可以再測試一些邊緣情況,看看數(shù)據(jù)庫能否正確處理異常數(shù)據(jù),比如:
例如,如果用戶嘗試添加重復主鍵,我們預期應拋出 ValueError 異常。因此編寫測試如下:
為了滿足以上測試,代碼需要稍作修改。特別是按照 id 查找主鍵是個常用操作,通過遍歷的方法效率太低了,更好是能夠通過主鍵直接訪問。因此在數(shù)據(jù)庫中再增加一個字典:
完整代碼請參考 Github 倉庫。
在上個步驟,我們在初始化數(shù)據(jù)庫時為節(jié)點明確指定了主鍵。按照數(shù)據(jù)庫設計的一般原則,主鍵更好是不具有業(yè)務含義的代理主鍵( Surrogate key ),用戶不應該關心它具體的值是什么,因此讓數(shù)據(jù)庫去管理主鍵通常是更為合理的。當然,在部分場景下——比如導入外部數(shù)據(jù)——明確指定主鍵仍然是有用的。
為了同時支持這些要求,我們這樣約定:字段 _id 表示節(jié)點的主鍵,如果用戶指定了該字段,則使用用戶設置的值(當然,用戶有責任保證它們不會重復);否則,由數(shù)據(jù)庫自動為它分配一個主鍵。
如果主鍵是數(shù)據(jù)庫生成的,事先無法預知它的值是什么,而邊( edge )必須指定它所指向的節(jié)點,因此必須在主鍵生成后才能添加。由于這個原因,在動態(tài)生成主鍵的情況下,數(shù)據(jù)庫的初始化會略微復雜一些。還是先寫一個測試:
為支持此功能,我們在數(shù)據(jù)庫中添加一個內(nèi)部字段 _next_id 用于生成主鍵,并讓 add_node 方法返回新生成的主鍵:
接下來,再確認一下邊是否可以正常訪問:
運行測試,一切正常。這個步驟很輕松地完成了,不過兩個測試( DbModelTest 和 PrimaryKeyTest )出現(xiàn)了一些重復代碼,比如 get_item 。我們可以把這些公用代碼提取出來。由于 get_item 內(nèi)部調(diào)用了 TestCase.assertXXX 等方法,看起來應該使用繼承,但從 TestCase 派生基類容易引起一些潛在的問題,所以我轉(zhuǎn)而使用另一個技巧 Mixin :
實現(xiàn)數(shù)據(jù)庫模型之后,接下來就要考慮如何查詢它了。
在設計查詢時要考慮幾個問題。對于圖的訪問來說,幾乎總是由某個節(jié)點(或符合條件的某一類節(jié)點)開始,從與它相鄰的邊跳轉(zhuǎn)到其他節(jié)點,依次類推。所以鏈式調(diào)用對查詢來說是一種很自然的風格。舉例來說,要知道 Tom 的孫子養(yǎng)了幾只貓,可以使用類似這樣的查詢:
可以想象,以上每個方法都應該返回符合條件的節(jié)點。這種實現(xiàn)是很直觀的,不過存在一個潛在的問題:很多時候用戶只需要一小部分結(jié)果,如果它總是不計代價地給我們一個巨大的,會造成極大的浪費。比如以下查詢:
為了避免不必要的浪費,我們需要另外一種機制,也就是通常所稱的“懶式查詢”或“延遲查詢”。它的基本思想是,當我們調(diào)用查詢方法時,它只是把查詢條件記錄下來,而并不立即返回結(jié)果,直到明確調(diào)用某些方法時才真正去查詢數(shù)據(jù)庫。
如果讀者比較熟悉流行的 Python ORM,比如 SqlAlchemy 或者 Django ORM 的話,會知道它們幾乎都是懶式查詢的,要調(diào)用 list(result) 或者 result 這樣的方法才能得到具體的查詢結(jié)果。
在 Dagoba 中把觸發(fā)查詢的方法定義為 run 。也就是說,以下查詢執(zhí)行到 run 時才真正去查找數(shù)據(jù):
和懶式查詢( Lazy Query )相對應的,直接返回結(jié)果的方法一般稱作主動查詢( Eager Query )。主動查詢和懶式查詢的內(nèi)在查找邏輯基本上是相同的,區(qū)別只在于觸發(fā)機制不同。由于主動查詢實現(xiàn)起來更加簡單,出錯也更容易排查,因此我們先從主動查詢開始實現(xiàn)。
還是從測試開始。前面測試所用的簡單數(shù)據(jù)庫數(shù)據(jù)太少,難以滿足查詢要求,所以這一步先來創(chuàng)建一個更復雜的數(shù)據(jù)模型:
此關系的復雜之處之一在于反向關聯(lián):如果 A 是 B 的哥哥,那么 B 就是 A 的弟弟/妹妹,為了查詢到他們彼此之間的關系,正向關聯(lián)和反向關聯(lián)都需要存在,因此在初始化數(shù)據(jù)庫時需要定義的邊數(shù)量會很多。
當然,父子之間也存在反向關聯(lián)的問題,為了讓問題稍微簡化一些,我們目前只需要向下(子孫輩)查找,可以稍微減少一些關聯(lián)數(shù)量。
因此,我們定義數(shù)據(jù)模型如下。為了減少重復工作,我們通過 _backward 字段定義反向關聯(lián),而數(shù)據(jù)庫內(nèi)部為了查詢方便,需要把它維護成兩條邊:
然后,測試一個最簡單的查詢,比如查找某人的所有孫輩:
這里 outcome/income 分別表示從某個節(jié)點出發(fā)、或到達它的節(jié)點。在原作者的代碼中把上述方法稱為 out/in 。當然這樣看起來更加簡潔,可惜的是 in 在 Python 中是個關鍵字,無法作為函數(shù)名。我也考慮過加個下劃線比如 out_.in_ 這種形式,但看起來也有點怪異,權衡之后還是使用了稍微啰嗦一點的名稱。
現(xiàn)在我們可以開始定義查詢接口了。在前面已經(jīng)說過,我們計劃分別實現(xiàn)兩種查詢,包括主動查詢( Eager Query )以及延遲查詢( Lazy Query )。
它們的內(nèi)在查詢邏輯是相通的,看起來似乎可以使用繼承。不過遵循 YAGNI 原則,目前先不這樣做,而是只定義兩個新類,在滿足測試的基礎上不斷擴展。以后我們會看到,與繼承相比,把共同的邏輯放到數(shù)據(jù)庫本身其實是更為合理的。
接下來實現(xiàn)訪問節(jié)點的方法。由于 EagerQuery 調(diào)用查詢方法會立即返回結(jié)果,我們把結(jié)果記錄在 _result 內(nèi)部字段中。雖然 node 方法只返回單個結(jié)果,但考慮到其他查詢方法幾乎都是返回,為統(tǒng)一起見,讓它也返回,這樣可以避免同時支持與單結(jié)果的分支處理,讓代碼更加簡潔、不容易出錯。此外,如果查詢對象不存在的話,我們只返回空,并不視為一個錯誤。
查詢輸入/輸出節(jié)點的方法實現(xiàn)類似這樣:
查找節(jié)點的核心邏輯在數(shù)據(jù)庫本身定義:
以上使用了內(nèi)部定義的一些輔助查詢方法。用類似的邏輯再定義 income ,它們的實現(xiàn)都很簡單,讀者可以直接參考源碼,此處不再贅述。
在此步驟的最后,我們再實現(xiàn)一個優(yōu)化。當多次調(diào)用查詢方法后,結(jié)果可能會返回重復的數(shù)據(jù),很多時候這是不必要的。就像關系數(shù)據(jù)庫通常支持 unique/distinct 一樣,我們也希望 Dagoba 能夠過濾重復的數(shù)據(jù)。
假設我們要查詢某人所有孩子的祖父,顯然不管有多少孩子,他們的祖父應該是同一個人。因此編寫測試如下:
現(xiàn)在來實現(xiàn) unique 。我們只要按照主鍵把重復數(shù)據(jù)去掉即可:
在上個步驟,初始化數(shù)據(jù)庫指定了雙向關聯(lián),但并未測試它們。因為我們還沒有編寫代碼去支持它們,現(xiàn)在增加一個測試,它應該是失敗的:
運行測試,的確失敗了。我們看看要如何支持它。回想一下,當從邊查找節(jié)點時,使用的是以下方法:
這里也有一個潛在的問題:調(diào)用 self.edges 意味著遍歷所有邊,當數(shù)據(jù)庫內(nèi)容較多時,這是巨大的浪費。為了提高性能,我們可以把與節(jié)點相關的邊記錄在節(jié)點本身,這樣要查找邊只要看節(jié)點本身即可。在初始化時定義出入邊的:
在添加邊時,我們要同時把它們對應的關系同時更新到節(jié)點,此外還要維護反向關聯(lián)。這涉及對字典內(nèi)容的部分復制,先編寫一個輔助方法:
然后,將添加邊的實現(xiàn)修改如下:
這里的代碼同時添加正向關聯(lián)和反向關聯(lián)。有的朋友可能會注意到代碼略有重復,是的,但是重復僅出現(xiàn)在該函數(shù)內(nèi)部,本著“三則重構(gòu)”的原則,暫時不去提取代碼。
實現(xiàn)之后,前面的測試就可以正常通過了。
在這個步驟中,我們來實現(xiàn)延遲查詢( Lazy Query )。
延遲查詢的要求是,當調(diào)用查詢方法時并不立即執(zhí)行,而是推遲到調(diào)用特定方法,比如 run 時才執(zhí)行整個查詢,返回結(jié)果。
延遲查詢的實現(xiàn)要比主動查詢復雜一些。為了實現(xiàn)延遲查詢,查詢方法的實現(xiàn)不能直接返回結(jié)果,而是記錄要執(zhí)行的動作以及傳入的參數(shù),到調(diào)用 run 時再依次執(zhí)行前面記錄下來的內(nèi)容。
如果你去看作者的實現(xiàn),會發(fā)現(xiàn)他是用一個數(shù)據(jù)結(jié)構(gòu)記錄執(zhí)行操作和參數(shù),此外還有一部分邏輯用來分派對每種結(jié)構(gòu)要執(zhí)行的動作。這樣當然是可行的,但數(shù)據(jù)處理和分派部分的實現(xiàn)會比較復雜,也容易出錯。
本文的實現(xiàn)則選擇了另外一種不同的方法:使用 Python 的內(nèi)部函數(shù)機制,把一連串查詢變換成一組函數(shù),每個函數(shù)取上個函數(shù)的執(zhí)行結(jié)果作為輸入,最后一個函數(shù)的輸出就是整個查詢的結(jié)果。由于內(nèi)部函數(shù)同時也是閉包,盡管每個查詢的參數(shù)形式各不相同,但是它們都可以被閉包“捕獲”而成為內(nèi)部變量,所以這些內(nèi)部函數(shù)可以采用統(tǒng)一的形式,無需再針對每種查詢設計額外的數(shù)據(jù)結(jié)構(gòu),因而執(zhí)行過程得到了很大程度的簡化。
首先還是來編寫測試。 LazyQueryTest 和 EagerQueryTest 測試用例幾乎是完全相同的(是的,兩種查詢只在于內(nèi)部實現(xiàn)機制不同,它們的調(diào)用接口幾乎是完全一致的)。
因此我們可以把 EagerQueryTest 的測試原樣不變拷貝到 LazyQueryTest 中。當然拷貝粘貼不是個好注意,對于比較冗長而固定的初始化部分,我們可以把它提取出來作為兩個測試共享的公共函數(shù)。讀者可參考代碼中的 step04_lazy_query/tests/test_lazy_query.py 部分。
程序把查詢函數(shù)的串行執(zhí)行稱為管道( pipeline ),用一個變量來記錄它:
然后依次實現(xiàn)各個調(diào)用接口。每種接口的實現(xiàn)都是類似的:用內(nèi)部函數(shù)執(zhí)行真正的查詢邏輯,再把這個函數(shù)添加到 pipeline 調(diào)用鏈中。比如 node 的實現(xiàn)類似下面:
其他接口的實現(xiàn)也與此類似。最后, run 函數(shù)負責執(zhí)行所有查詢,返回最終結(jié)果;
完成上述實現(xiàn)后執(zhí)行測試,確保我們的實現(xiàn)是正確的。
在前面我們說過,延遲查詢與主動查詢相比,更大的優(yōu)勢是對于許多查詢可以按需要訪問,不需要每個步驟都返回完整結(jié)果,從而提高性能,節(jié)約查詢時間。比如說,對于下面的查詢:
以上查詢的意思是從孫輩中找到一個符合條件的節(jié)點即可。對該查詢而言,主動查詢會在調(diào)用 outcome(‘son’) 時就遍歷所有節(jié)點,哪怕最后一步只需要之一個結(jié)果。而延遲查詢?yōu)榱颂岣咝?,應在找到符合條件的結(jié)果后立即停止。
目前我們尚未實現(xiàn) take 方法。老規(guī)矩,先添加測試:
主動查詢的 take 實現(xiàn)比較簡單,我們只要從結(jié)果中返回前 n 條記錄:
延遲查詢的實現(xiàn)要復雜一些。為了避免不必要的查找,返回結(jié)果不應該是完整的列表( list ),而應該是個按需返回的可迭代對象,我們用內(nèi)置函數(shù) next 來依次返回前 n 個結(jié)果:
寫完后運行測試,確保它們是正確的。
從外部接口看,主動查詢和延遲查詢幾乎是完全相同的,所以用單純的數(shù)據(jù)測試很難確認后者的效率一定比前者高,用訪問時間來測試也并不可靠。為了測試效率,我們引入一個節(jié)點訪問次數(shù)的概念,如果延遲查詢效率更高的話,那么它應該比主動查詢訪問節(jié)點的次數(shù)更少。
為此,編寫如下測試:
我們?yōu)?Dagoba 類添加一個成員來記錄總的節(jié)點訪問次數(shù),以及兩個輔助方法,分別用于獲取和重置訪問次數(shù):
然后瀏覽代碼,查找修改點。增加計數(shù)主要在從邊查找節(jié)點的時候,因此修改部分如下:
此外還有 income/outcome 方法,修改都很簡單,這里就不再列出。
實現(xiàn)后再次運行測試。測試通過,表明延遲查詢確實在效率上優(yōu)于主動查詢。
不像關系數(shù)據(jù)庫的結(jié)構(gòu)那樣固定,圖的形式可以千變?nèi)f化,查詢機制也必須足夠靈活。從原理上講,所有查詢無非是從某個節(jié)點出發(fā)按照特定方向搜索,因此用 node/income/outcome 這三個方法幾乎可以組合出任意所需的查詢。
但對于復雜查詢,寫出的代碼有時會顯得較為瑣碎和冗長,對于特定領域來說,往往存在更為簡潔的名稱,例如:母親的兄弟可簡稱為舅舅。對于這些場景,如果能夠類似 DSL (領域特定語言)那樣允許用戶根據(jù)專業(yè)要求自行擴展,從而簡化查詢,方便閱讀,無疑會更為友好。
如果讀者去看原作者的實現(xiàn),會發(fā)現(xiàn)他是用一種特殊語法 addAlias 來定義自己想要的查詢,調(diào)用方法時再進行查詢以確定要執(zhí)行的內(nèi)容,其接口和內(nèi)部實現(xiàn)都是相當復雜的。
而我希望有更簡單的方法來實現(xiàn)這一點。所幸 Python 是一種高度動態(tài)的語言,允許在運行時向類中增加新的成員,因此做到這一點可能比預想的還要簡單。
為了驗證這一點,編寫測試如下:
無需 Dagoba 的實現(xiàn)做任何改動,測試就可以通過了!其實我們要做的就是動態(tài)添加一個自定義的成員函數(shù),按照 Python 對象機制的要求,成員函數(shù)的之一個成員應該是名為 self 的參數(shù),但這里已經(jīng)是在 UnitTest 的內(nèi)部,為了和測試類本身的 self 相區(qū)分,新函數(shù)的參數(shù)增加了一個下劃線。
此外,函數(shù)應返回其所屬的對象,這是為了鏈式調(diào)用所要求的。我們看到,動態(tài)語言的靈活性使得添加新語法變得非常簡單。
到此,一個初具規(guī)模的圖數(shù)據(jù)庫就形成了。
和原文相比,本文還缺少一些內(nèi)容,比如如何將數(shù)據(jù)庫序列化到磁盤。不過相信讀者都看到了,我們的數(shù)據(jù)庫內(nèi)部結(jié)構(gòu)基本上是簡單的原生數(shù)據(jù)結(jié)構(gòu)(列表+字典),因此序列化無論用 pickle 或是 ON 之類方法都應該是相當簡單的。有興趣的讀者可以自行完成它們。
我們的圖數(shù)據(jù)庫實現(xiàn)為了提高查詢性能,在節(jié)點內(nèi)部存儲了邊的指針(或者說引用)。這樣做的好處是,無論數(shù)據(jù)庫有多大,從一個節(jié)點到相鄰節(jié)點的訪問是常數(shù)時間,因此數(shù)據(jù)訪問的效率非常高。
但一個潛在的問題是,如果數(shù)據(jù)庫規(guī)模非常大,已經(jīng)無法整個放在內(nèi)存中,或者出于安全性等原因要實現(xiàn)分布式訪問的話,那么指針就無法使用了,必須要考慮其他機制來解決這個問題。分布式數(shù)據(jù)庫無論采用何種數(shù)據(jù)模型都是一個棘手的問題,在本文中我們沒有涉及。有興趣的讀者也可以考慮 500lines 系列中關于分布式和集群算法的其他一些文章。
本文的實現(xiàn)和系列中其他數(shù)據(jù)庫類似,采用 Python 作為實現(xiàn)語言,而原作者使用的是 JavaScript ,這應該和作者的背景有關。我相信對于大多數(shù)開發(fā)者來說, Python 的對象機制比 JavaScript 基于原型的語法應該是更容易閱讀和理解的。
圖數(shù)據(jù)庫和關系數(shù)據(jù)庫的區(qū)別
圖形數(shù)據(jù)庫和JanusGraph第1集。#圖數(shù)胡激據(jù)庫# #編程# #程序員# #架構(gòu)師##數(shù)據(jù)褲桐襪架構(gòu)# #JanusGraph# #DBA##大數(shù)據(jù)#
1、數(shù)據(jù)bai存儲方式不同。
關系型和非關系型數(shù)據(jù)庫的主要差異是數(shù)據(jù)存儲的方式。關系型數(shù)據(jù)天然就是表格式的,因此存儲在數(shù)據(jù)表的行和列中。數(shù)據(jù)表可以彼此關聯(lián)協(xié)作存儲,也很容易提取數(shù)據(jù)。
與其相反,非關系型數(shù)據(jù)不適合存儲在數(shù)據(jù)表的行和列中,而是大塊組合在一起。非關系型數(shù)據(jù)通常存儲在數(shù)據(jù)集中,就像文檔、鍵值對或者圖結(jié)構(gòu)。你的液首數(shù)據(jù)及其特性是選擇數(shù)據(jù)存儲和提取方式的首要影響因素。
2、擴展方式不同。
SQL和NoSQL數(shù)據(jù)庫更大的差別可能是在擴展方式上,要支持日益增長的需求當然要擴展。
要支持更多并發(fā)量,SQL數(shù)據(jù)庫是縱向擴展,也就是說提高處理能力,使用速度更快速的計算機,這樣處理相培埋迅同的數(shù)據(jù)集就更快了。
因為數(shù)據(jù)存儲在關系表中,操作的性能瓶頸可能涉及很多個表,這都需要通過提高計算機性能來客服。雖然SQL數(shù)據(jù)庫有很大擴展空間,但最終肯定會達到縱向擴展的上限。而NoSQL數(shù)據(jù)庫是橫向擴展的。
而非關系型數(shù)據(jù)存儲天然就是分布式的,NoSQL數(shù)據(jù)庫的擴展可以通過給資源池添加更多普通的數(shù)據(jù)庫服務器(節(jié)點)來分擔負載。
3、對事務性的支持不同。
如果數(shù)據(jù)操作需要高事務性或者復雜數(shù)據(jù)查詢需要控制執(zhí)行計劃,那么傳統(tǒng)的SQL數(shù)據(jù)庫從性能和穩(wěn)定性方面考慮是你的更佳選擇。SQL數(shù)據(jù)庫支持對事務原子性細粒度控制,并且易于回滾事務。
關于圖數(shù)據(jù)庫功能嗎的介紹到此就結(jié)束了,不知道你從中找到你需要的信息了嗎 ?如果你還想了解更多這方面的信息,記得收藏關注本站。
成都創(chuàng)新互聯(lián)科技有限公司,是一家專注于互聯(lián)網(wǎng)、IDC服務、應用軟件開發(fā)、網(wǎng)站建設推廣的公司,為客戶提供互聯(lián)網(wǎng)基礎服務!
創(chuàng)新互聯(lián)(www.cdcxhl.com)提供簡單好用,價格厚道的香港/美國云服務器和獨立服務器。創(chuàng)新互聯(lián)成都老牌IDC服務商,專注四川成都IDC機房服務器托管/機柜租用。為您精選優(yōu)質(zhì)idc數(shù)據(jù)中心機房租用、服務器托管、機柜租賃、大帶寬租用,可選線路電信、移動、聯(lián)通等。
當前題目:了解圖數(shù)據(jù)庫的功能和優(yōu)勢(圖數(shù)據(jù)庫功能嗎)
標題來源:http://www.5511xx.com/article/cochiec.html


咨詢
建站咨詢
