近期需要處理圖數據,考察後打算使用neo4j, 相比其他一些圖資料庫,neo4j開源,跨平臺,介面友好,文檔齊全,完整支持ACID。 首先放一張網上的圖片,關係型資料庫與圖資料庫存儲網路數據的差異:

初次接觸neo4j 踩了不少坑,這裡記錄一下。 關於如何安裝Neo4j和使用web ui進行查詢操作就不再贅述。

Cypher基本操作

相比關係型資料庫的SQL查詢語言,Neo4j的查詢語言為Cypher,語法更加友好,更適合圖數據做查詢操作。首先介紹在圖數據裡面幾個概念:

  • 節點(Node): 使用小括弧表示(n)表示n這個節點,同時一般都會賦予節點某個標籤(Label), 等同於關係書庫裡面的表名。比如(n: Person)表示n是一個Person類的節點,當然一個節點可以同時有多個label.
  • 關係(Relation):關係使用中括弧表示[r:Knows]表示rKnows這種關係。兩個節點的關係用--表示,如果有方向的話,加個箭頭即可。如(a)-[r:Knowns]->(b)表示節點a和b之間有r關係,其中Knownsr的類型
  • 屬性(Property): 節點和關係都可以附帶屬性,這個也是圖資料庫的優勢,儲存屬性非常方便,直接用key-value表示即可。比如(n:Person{name:"John"})表示name為John的節點n。同樣關係也可以有屬性:[r:Knows]{year: 2018}表示為r賦予一個year屬性。

幾個常用的關鍵字介紹:

  • MATCH: 表示查詢,是讀資料庫操作。比如查屬於Person的節點:MATCH (n:Person),查找姓名為"John"的節點:MATCH (n:Person){name: "John"}或者使用where語句:MATCH (n:Person) WHERE n.name="John"。當然這裡面很多語法可以使用,比如正則匹配等,這裡就不再贅述了。當然在實際使用中,MATCH不能單獨使用,需要結合RETURN
  • CREATE: 表示創建,可以新增節點,關係,索引,約束等等,是一種寫操作。比如CREATE (n:Person{name:"Ana"})表示創建一個name為"Ana"的Person節點。在創建的同時可以設置屬性:CREATE (n:Person{name:"Ana"}) set n.age=20。同樣在某個屬性上創建索引:CREATE INDEX ON :Person(name),這裡需要提一下,盡量所有的Label都設置索引或者UNIQUE約束,在後續的讀操作比如MATCH會大大提高性能(創建索引可以在導入節點之前執行)。
  • DELETE: 表示刪除節點,關係等,也是寫操作。一般需要結合MATCH匹配查詢要刪除的節點。MATCH (n:Person) DELETE n。如果在刪除有關係的節點,這樣刪除會報錯,可以先刪除邊MATCH (n:Person)-[r:KNOWS]->() DELETE r再刪除節點。不過更推薦使用DETACH DELETE來級聯刪除,MATCH (n:Person) DETACH DELETE n可以同時刪除節點及節點的關係。
  • MERGE:合併節點或者關係,屬於先讀後寫操作,相當於MATCH + CREATE,先檢查資料庫中節點/關係是否存在,如果存在的話就不再創建,反之執行CREATE。如:MERGE(a:Person{name:"John"}) on create set a.age=20 //創建節點,先檢測是否存在 // 給節點a,b建立關係,如果a,b已經存在,就無需新建。 MATCH (a:Person{name:"John"}), (b:Person{name:"Ana"}) MERGE (a)-[:KNOWS]->(b)

這幾個只是最基本的操作,在複雜查詢中,會用到諸如WITH, UNWIND等命令。這裡不再詳細描述。

幾個注意事項:

  • 節點名稱與節點Label的定義容易混亂。比如CREATE (n:Person)創建了一個屬於Person的節點n。這裡的n僅僅屬於一個變數名,跟節點本身沒有關係,命令執行結束,n的生命週期也就結束了,而Person則是節點本身的Label,會一直存在。
  • 索引一定要建立

關於Neo4j瀏覽器的初次使用有幾個快捷鍵:

  • 默認單行輸入,按回車執行命令
  • 輸入一行命令之後,按SHIFT + ENTER進入多行輸入狀態
  • 在多行輸入時,CTRL + ENTER執行命令
  • ESC可以放大輸入框至屏幕大小,複雜查詢的時候,很方便。

關於內存配置的幾個參數內存配置:

  • dbms.memory.heap.initial_size
  • dbms.memory.heap.max_size
  • dbms.memory.pagecache.size可以使用neo4j-admin memrec來根據當前資料庫數據,查看推薦的內存配置

bin/neo4j-admin memrec --database=graph.db

導入數據

這一部分主要記錄下如何將圖數據從文件中導入庫,常見的格式為CSV和JSON格式。

導入CSV 格式數據

Neo4j內置了命令來導入CSV數據:使用方法也很簡單。假設CSV格式如下:

"Id","Name","Year"
"1","ABBA","1992"
"2","Roxette","1986"
"3","Europe","1979"
"4","The Cardigans","1992"

直接使用如下命令導入並直接引用headers來表示屬性並創建節點:

LOAD CSV WITH HEADERS FROM FILE:/artists.csv AS line
CREATE (:Artist { name: line.Name, year: toInteger(line.Year)})

注意事項:

  • 分隔符默認是,, 可以用FIELDTERMINATOR自定義分隔符:LOAD CSV WITH HEADERS FROM FILE:/artists.csv AS line FIELDTERMINATOR ";"
  • 文件位置: 可以直接使用URL地址作為文件位置,如果是本地文件的話,直接使用"FILE:"表明,文件的位置是相對位置,在配置文件neo4j.conf中的dbms.directories.import參數可以指定,默認是neo4j安裝目錄下的import文件夾,將CSV文件放到該目錄下即可。
  • 對於大規模數據,如果一次性導入可能會超內存,此時可以用PERIODIC COMMIT來分批提交導入數據,默認是1000行提交一次,具體如下:

USING PERIODIC COMMIT 500
LOAD CSV WITH HEADERS FROM...
....

  • 文本內容存在"的欄位需要特殊處理

導入JSON格式數據

圖數據裡面更常見的則是JSON數據, 假設數據格式如下:

[
{
"id":1, "friends":[2,3], "name": "Bob", "age": 27,
"book":[{"name":"book1", "year":2000}, {"name":"book3", "year":1990}]
},
{
"id":2, "friends":[1], "name": "Alice", "age": 29,
"book":[{"name":"book1", "year":2000}, {"name":"book2", "year":1999}]
},
{
"id":3, "friends":[2], "name": "John", "age": 20,
"book":[{"name":"book3", "year":1990}]
}
]

列表中每一個map都代表一個User, 其屬性有id,name,age; 同時friends欄位表示朋友關係,book欄位表示讀過某本書。 現在我們需要創建 Person 和 Book兩類節點,同時Person和Book 之間有READ關係。 Neo4j 並沒有內置直接導入Json的函數,不過在Neo3.3版本之後,推出了一個函數存儲包APOC,裡麪包含了非常豐富的函數和存儲過程,如各種圖計算演算法,是Cypher的有力補充,其中就包含了從Json中導入數據。安裝APOC很簡單,只需要三步:

  • 從github中下載與Neo4j對應版本的APOCjar包
  • 將jar包拷貝到neo4j安裝目錄的plugins目錄下
  • 在配置文件neo4j.conf中加入一行允許APOC導入文件:apoc.import.file.enabled=true
  • 重啟Neo4j即可

在Neo4j瀏覽器中,輸入return apoc.version()即可查看版本號

此外我們可以看到apoc支持導入非常多格式的數據:

導入方式很簡單,我們要創建兩類節點,首先創建索引,方便後續導入。

CREATE INDEX ON :Person(id)
CREATE INDEX ON :Book(name)

導入代碼如下:

// YIELD關鍵字表示每次導入json數據中的一組數據,即`[...]`中的每一個`{}`
CALL apoc.load.json("file:///person.json") YIELD value as person
// 需要對book屬性進行列表展開,後續建立Person和Book關係的時候,需要用。
UNWIND person.book as book
// 創建Person節點
MERGE (p:Person{id:person.id})
SET p.name=person.name, p.age=person.age, p.friends=person.friends
//創建book節點
MERGE (b:Book{name:book.name})
SET b.year=book.year
//建立person->book關係
MERGE (p)-[:READ]->(b)

然後再根據已有的friends導入Friend關係:

//對每一個 person遍歷
MATCH (p:Person)
// 對p的friends進行列表展開,
UNWIND p.friends as f
// 根據id搜索Person節點
MATCH (q:Person{id:f})
// 建立關係
MERGE (p)-[:Friend]-(q)

執行按成之後,可視化看一下 ,:

到這裡導入基本完成了,不過還有一點問題,暫時沒有解決,使用UNWIND person.book as book的時候,如果某個節點沒有book這個一個屬性,那麼後續代碼將不再執行,即該Person節點不會創建。但是如果將UNWIND放到創建Person之後,建立的READ關係會有問題,還在查找原因。

參考資源

  • 官方文檔
  • APOC
  • Neo4j應用

推薦閱讀:

相關文章