背景
最近我在嘗試存儲知識圖譜的過程中,接觸到了Neo4j圖數據庫,這里我摘取了一段Neo4j的簡介:
Neo4j是一個高性能的,NOSQL圖形數據庫,它將結構化數據存儲在網絡上而不是表中。它是一個嵌入式的、基於磁盤的、具備完全的事務特性的Java持久化引擎,但是它將結構化數據存儲在網絡(從數學角度叫做圖)上而不是表中。Neo4j也可以被看作是一個高性能的圖引擎,該引擎具有成熟數據庫的所有特性。程序員工作在一個面向對象的、靈活的網絡結構下而不是嚴格、靜態的表中——但是他們可以享受到具備完全的事務特性、企業級的數據庫的所有好處。
在下載了Neo4j Server(3.4.1)以后,我開始着手把手頭的三元組數據存儲進neo4j的數據庫中,用的是python的py2neo庫,我的思路是:讀取文件,將每行的實體抽取出來,在圖中查找是否有該(兩個)實體節點,如果不存在就插入節點,然后插入該行三元組表示的邊。
但是這樣做的效率很低(我的圖要至少連續一個月才能存完)。我分析了一下,原因在於:每次插入實體節點都需要先查詢圖中是否存在該實體節點,隨着圖的增大,查詢所需的時延也越來越長。
在查了官方文檔以后,我找到了一個高效率的導入數據的方法–neo4j的import工具,這里我將我在導入過程中遇到的問題和我的解決方案和分析分享出來,供大家參考。
導入方法
import工具命令為如下格式:
neo4j-admin import [--mode=csv] [--database=<name>] [--additional-config=<config-file-path>] [--report-file=<filename>] [--nodes[:Label1:Label2]=<"file1,file2,...">] [--relationships[:RELATIONSHIP_TYPE]=<"file1,file2,...">] [--id-type=<STRING|INTEGER|ACTUAL>] [--input-encoding=<character-set>] [--ignore-extra-columns[=<true|false>]] [--ignore-duplicate-nodes[=<true|false>]] [--ignore-missing-nodes[=<true|false>]] [--multiline-fields[=<true|false>]] [--delimiter=<delimiter-character>] [--array-delimiter=<array-delimiter-character>] [--quote=<quotation-character>] [--max-memory=<max-memory-that-importer-can-use>] [--f=<File containing all arguments to this import>] [--high-io=<true/false>]
或:
neo4j-admin import --mode=database [--database=<name>] [--additional-config=<config-file-path>] [--from=<source-directory>]
方括號內為可以選擇的參數,其中我們常用的是第一種格式,即從獨立的文件里導入圖數據,常用參數為--nodes
和--relationships
,分別用來引入節點的CSV文件和邊的CSV文件。
舉個例子:
bin/neo4j-admin import --nodes <filepath of the csv file of nodes> --relationships <filepath of the csv file of relationships>
1. 生成CSV文件
Neo4j的import工具要求數據使用CSV文件保存,因此在導入數據前需要將數據轉乘CSV文件。節點和關系需要不同的文件,一種節點的CSV文件可以分為多個文件儲存,傳遞參數的時候需要按順序加上所有文件的文件名(絕對路徑),工具讀取第一個文件的表頭作為節點/邊的屬性名,該文件剩下所有行以及后續文件的所有行作為屬性值。多種節點(如包含不同屬性集合)的導入需要分別為每一種節點分別引入(即使用多次--node
參數)。
在我的需求中,節點和邊都只包含一個名稱(數據格式每行為<subject>\t<predicate>\t<object> .
),因此我將節點和邊分別僅用一個CSV文件儲存,使用python的csv庫,csv庫寫csv文件的方法為(以下代碼不可執行):
import csv
csvf = open(filepath,'w',newline='',encoding='utf-8')
w = csv.writer(csvf)
w.writerow((column_name_1, column_name_2, ...))#寫入表頭
for i in some_range:
w.writerow((column_1, column_2, ...))#寫入行
csvf.close()
注意,writerow傳入的參數為含有多個字符串的tuple,而不是多個字符串。
我生成的CSV文件表頭結構如下:
node.csv: name:ID(node), :LABEL
rel.csv: :START_ID(node), :END_ID(node), :TYPE,name
其中:
name:ID
表示該列的屬性名為name,ID
表示該屬性是唯一標示一個實體的屬性(類似關系型數據庫中的主碼),括號表示一個id-group,即表示該ID唯一表示括號內種類的實體,而不是所有實體;:LABEL
表示節點的標簽;START_ID
和END_ID
表示邊的起點和終點的ID,可以加上它們各自的id-group;:TYPE
表示該邊的種類,注意種類個數不應超過65535。
2.使用import指令導入
在CSV文件准備就緒以后,打開電腦的終端,執行:
1.改變工作目錄至Neo4j的根目錄,(打開該目錄以后應當能看到bin, conf, data, import, lib等文件夾):
cd filepath_to_neo4j_home_directory
2.運行neo4j-admin import指令,注意:
在此之前應當保證在Neo4j的目錄下的data/databases/graph.db
下沒有文件,即該指令要求數據庫為空;
Neo4j應當關閉,處於stopped狀態(關閉方法:終端在Neo4j的根目錄執行./bin/neo4j stop
),
--nodes
和--relationships
后的文件名應當是絕對路徑。
bin/neo4j-admin import --nodes some_path_to/node.csv --relationships some_path_to/rel.csv
不出意料,該指令執行結果是:
3.重新啟動Neo4j,進入瀏覽器輸入IP+端口(默認為http://localhost:7474/)查看結果,啟動的指令是:
./bin/neo4j start
如果正確操作,且不出意外的話,在瀏覽器中應當能查詢正確導入的圖譜:
總結&注意事項
1.傳入文件名的時候務必使用絕對路徑,否則將會拋出以下錯誤:
Expected '--nodes' to have at least 1 valid item, but had 0 []
Expected '--relationships' to have at least 1 valid item, but had 0 []
2.使用neo4j-admin import指令導入之前先將原數據庫從neo4j_home/data/databases/graph.db/中移除,即指令要求目錄下不含數據庫,否則指令無法執行;
3.在執行指令之前務必保證Neo4j處於關閉狀態,如果不確定可以在Neo4j根目錄下運行./bin/neo4j status
查看當前狀態。如果數據庫未關閉,可能會導致數據庫即使成功導入,也無法查詢到(我的經驗是這樣);
4.寫CSV文件的時候務必確保所有的節點的CSV文件的ID fileds的值都唯一、不重復(類似SQL中的primary key),並且確保所有的邊的CSV文件的START_ID 和 END_ID都包含在節點CSV文件中(參考SQL中的referential integrity constraint);
5.若要使用其他參數請參考官方的文檔,我在下面的References中給出;
6.我的Neo4j運行環境是OS X系統,如果你使用的是其他操作系統,指令可能有所不同,比如啟動和終止服務的指令。
References:
[1]https://neo4j.com/developer/guide-import-csv/
[2]https://neo4j.com/docs/operations-manual/current/tutorial/import-tool/