概述
1、Neo4j是一個高性能的,NOSQL圖形數據庫,它將結構化數據存儲在網絡上而不是表中。它是一個嵌入式的、基於磁盤的、具備完全的事務特性的Java持久化引擎,但是它將結構化數據存儲在網絡(從數學角度叫做圖)上而不是表中。
2、Neo4j也可以被看作是一個高性能的圖引擎,該引擎具有成熟數據庫的所有特性。程序員工作在一個面向對象的、靈活的網絡結構下而不是嚴格、靜態的表中——但是他們可以享受到具備完全的事務特性、企業級的數據庫的所有好處。(摘自百度百科)
3、Neo4j圖數據庫遵循屬性圖模型來存儲和管理其數據。
屬性圖模型規則
- 表示節點,關系和屬性中的數據
- 節點和關系都包含屬性
- 關系連接節點
- 屬性是鍵值對
- 節點用圓圈表示,關系用方向鍵表示。
- 關系具有方向:單向和雙向。
- 每個關系包含“開始節點”或“從節點”和“到節點”或“結束節點”
- 在屬性圖數據模型中,關系應該是定向的。如果我們嘗試創建沒有方向的關系,那么它將拋出一個錯誤消息。
4、在Neo4j中,關系也應該是有方向性的。如果我們嘗試創建沒有方向的關系,那么Neo4j會拋出一個錯誤消息,“關系應該是方向性的”。
5、Neo4j圖數據庫將其所有數據存儲在節點和關系中。我們不需要任何額外的RRBMS數據庫或無SQL數據庫來存儲Neo4j數據庫數據。它以圖形的形式存儲其數據的本機格式。
6、圖形數據庫數據模型的主要構建塊是:節點、關系、屬性
7、neo4j主要存儲節點和關系,其中關系必須為有向關系,描述節點和關系的數據以屬性的形式存儲,節點和關系上都能放鍵值對的屬性。不同類型的節點和關系通過標簽Label來區別,不同標簽的節點代表不同類型節點,不同標簽關系代表不同類型關系,示例:
創建一個標簽為Person的節點,其有屬性 name 和age:create (:Person{name:'小紅',age:21});
查詢一個節點:match (m:Person{name:'小紅',age:21}) return n;
刪除一個節點:match (m:Person{name:'小紅',age:21}) delete n;
創建關系:create (a:Person{name:"a"}),(b:Person{name:"b"}) with a,b create (a)-[r:Friend]->(b);
查詢關系:match (a:Person{name:"a"})-[r:Friend]->(b:Person{name:"b"}) return r;
刪除關系:match p=(a:Person{name:"a"})-[r:Friend]->(b:Person{name:"b"}) delete p;
其中create還有個近似的操作merge,也可以創建數據,其中merge可以看做match和create的合體。merge會先去原始庫match屬性或標簽,如果不存在會創建,可以結合on create 和on match使用,如:
- 在創建的時候使用on create(在創建時進行一些操作)---如果存在name為Keanu Reeves的Person節點,則新增或修改屬性created 屬性,否則新增節點
MERGE (keanu:Person { name: 'Keanu Reeves' }) ON CREATE SET keanu.created = timestamp() RETURN keanu.name, keanu.created
- 在創建的時候使用 on match---如果有Person標簽的節點,則更新found 屬性,否則不做操作
MERGE (person:Person) ON MATCH SET person.found = TRUE RETURN person.name, person.found
- 同時使用on create 和 on match---如果存在name為Keanu Reeves的Person節點,則執行on match操作,否則執行on create操作
MERGE (keanu:Person { name: 'Keanu Reeves' }) ON CREATE SET keanu.created = timestamp() ON MATCH SET keanu.lastSeen = timestamp() RETURN keanu.name, keanu.created, keanu.lastSeen
插入數據
目前主要有以下幾種數據插入方式:
- Cypher create 語句,為每一條數據寫一個create
- Cypher load csv 語句,將數據轉成CSV格式,通過LOAD CSV讀取數據。
- 官方提供的neo4j-import工具,未來將被neo4j-admin import代替
- 官方提供的Java API - BatchInserter
- 大牛編寫的 batch-import 工具
- neo4j-apoc load.csv + apoc.load.relationship
just try | create語句 | load csv語句 | neo4j-import | BatchInserter | batch-import | apoc |
---|---|---|---|---|---|---|
適用場景 | 1 ~ 1w | 0 ~ 1000w | 千萬以上 | 千萬以上 | 千萬以上 | 1 ~ 數千萬 |
速度 | 很慢 1000/s | 一般 5000/s | 非常快 x w/s | 很快 x w/s | 很快x w/s | 1w /s |
實際測試 | 無 | 9.5k/s(節點+關系) 用到了merge,數據量越大,速度越慢 |
12w/s(節點+關系) | 1w/s(節點+關系) | 1w/s(節點+關系) | 4k/s(1億數據上增量更新) 1w/s(百萬數據上更新) 用到了merge,數據量越大,速度越慢 |
優點 | 1.使用方便 2.可實時插入 |
1.官方ETL工具 2.可以加載本地/遠程CSV 3.可實時插入 |
1.官方工具 2.占用資源少 |
1.官方API | 1.可以增量更新 2.基於BatchInserter |
1.官方ETL工具 2.可以增量更新 3.支持在線導入 4.支持動態傳Label RelationShip |
缺點 | 1.速度慢 2.處理數據,拼CQL復雜,很少使用 |
1.導入速度較慢 2.只能導入節點 3.不能動態傳Label RelationShip |
1.需要脫機導入 停止Neo4j數據庫 2.只能用於初始化導入 |
1.只能在JAVA中使用 2.需要脫機導入 停止Neo4j數據庫 |
1.需要脫機導入 停止Neo4j數據庫 | 1.速度一般 |
比對:
- neo4j-import導入速度快,但是要求是空庫,導入時要停止neo4j,也就是脫機導入,而且你要提前處理好數據,數據最好不要有重復,如果有重復,可以導入時跳過,然后根據bad.log來查看或者修正這部分數據
- batch-import可以增量導入,但是要求導入時停止neo4j數據庫(脫機導入),而且增量更新的數據不會和庫里存在的數據對比,所以要求數據全是新的,否則會出現重復數據
- load csv比較通用,而且可以在neo4j數據庫運行時導入,但是導入速度相對較慢,要提前整理好數據,而且不能動態創建 Label RelationShip
- apoc挺好用的,可以動態創建RelationShip,但是不能動態創建Label (動態創建Label只能在程序里通過拼接字符串的方法實現)
實際情況中,處理數據比導入數據更花費時間
(這里介紹我使用過的create、neo4j-import、load csv方法)
1、neo4j-import批量導入,如上所述,導入很快,需要停止neo4j服務才能進行導入,否則會導入失敗(適合用於初始化,否則會影響正在使用的服務);需要導入的庫不存在,如果存在需要刪除對應的.db文件再執行,否則導入會失敗;需要事先把要導入的數據按特定格式整理好放入csv中(這里可以參照ba-es項目的service.KnCallsBulkPut類),這里以導入通話記錄關系為例
節點csv(person.csv)格式:(其中節點標簽為Person,屬性有personId、name,且personId的值為節點ID)
personId:ID(Person),name
13661909859,鄭敏
angeloo68@163.com,李曉芊
15107253239,胡強
關系csv(rels.csv)格式:(其中:START_ID為關系起始點的id,對應上面節點的ID,:END_ID為關系結束點的id,對應上面節點的ID,in,out分別別關系的屬性)
:START_ID(Person),in,out,:END_ID(Person)
13545342144,0,300,15207123026
18016315333,0,50000,1529703684@qq.com
13661909859,0,2000.0,855-0979212256
13661909859,0,22210.0,15880082973
執行初始化批量錄入命令:
.../neo4j-community-3.3.5/bin/neo4j-import —multiline-fields=true —bad-tolerance=0 —into .../neo4j-community-3.3.5/data/databases/caifen.db —nodes:Pesron .../person.csv —relationships:CALLWITH .../rels.csv (其中bad-tolerance指定你能容忍的錯誤數據量,如果對數據精度要求不高可以放大,這樣在導入過程中,即使有小於設置值數量的數據錯誤,導入不報錯,正確的數據能導入成功。.../neo4j-community-3.3.5/data/databases目錄下如果已經有caifen.db,執行命令會報錯,需要手動刪除。--nodes:、--relationships:可以拼接多個,后面跟的分別是節點標簽和關系標簽,再后面跟着的是對應生成對應節點和關系的csv文件)
2、load csv使用(當前公司用於導入蟲洞數據更新到已有庫里)
使用方式:
neo4j自帶客戶端瀏覽器訪問:http://ip:7474,如 http://192.168.0.246:7474
進到neo4j的安裝目錄的bin目錄下(需要先進到neo4j-community-3.3.5/conf/neo4j.conf放開dbms.shell.port的注釋,再重啟),執行neo4j-shell
把整理好的文檔放入對應目錄,然后通過上面兩種方式進入命令行執行:
[USING PERIODIC COMMIT 500] LOAD CSV [ WITH HEADERS] FROM “file:///rels20190717.csv”[W] AS line with line merge (n:Suspect{personId:line[0]}) on match set n.name=case when line[1] <> line[0] then line[1] else n.name end on create set n.personId=line[0],n.name=line[1] with line,n merge(m:Suspect{personId:line[2]}) on match set m.name=case when line[2] <> line[3] then line[3] else m.name end on create set m.personId=line[2],m.name=line[3] with n,m,line merge (n)-[r:CAPITALFLOWING]->(m)
說明:上面命令中[]里的命令為可選,[USING PERIODIC COMMIT 500] 表示 每500 行進行一次事務提交, [ WITH HEADERS] 導入時是否帶csv中的表頭,其中帶表頭用的是 line.name ,反之用的是 line[0]。導入命令中還可以用 toInt(‘1’) toFloat(‘1.0’)toInteger(), toFloat(), split()對數據進行處理;其中fill:后面跟的是文檔路徑,可以使絕對路徑也可以是相對路徑,neo4j默認是從neo4j-community-3.3.5/import中讀取文件的,上面示例的寫法就是將csv放在import文件夾下的寫法;windows下相對路徑方式如:file:/Test.csv,windows下絕對路徑方式如:file:///C:/User/wdb/2017-04-06_test.csv,linux下相對路徑格式:file:/2017-04-06_test.csv,linux下絕對路徑格式:file:/home/wkq/databases/data/2017-04-06_test.csv;
3、插入一條數據,前面已經介紹過了,上面兩種方式進入到命令行,執行create或merge進行數據插入,這里介紹unwind list+create(一條一條數據錄入)在java API中的使用(掃黑數據的錄入程序中)
if (phSet.size() > 0) { for (String eachph : phSet) { if (!MiscUtils.isNullOrEmpty(sfz)) { String temp = "{sfz:\"" + sfz + "\",name:\"" + name + "\",sex:\"" + sex + "\",ph:\"" + eachph + "\",rksj:\"" + rksj + "\"}"; sfzPhoneSet.add(temp); } } } ... StringBuilder sb = new StringBuilder(); // 存在擁有手機關系 if (sfzPhoneSet.size() > 0) { sb.append("UNWIND " + sfzPhoneSet + " as row with row "); sb.append( " merge (n1:Mobile{phone:row.ph}) on create set n1.phone=row.ph,n1.iskey=toString(0),n1.personId=row.ph,n1.rksj=row.rksj with n1,row "); sb.append( " merge (n2:Person{sfz:row.sfz}) on match set n2.name=case when row.name<> '' then row.name else n2.name end,n2.sex=case when row.sex<> '' then row.sex else n2.sex end,n2.rksj=case when row.rksj<> '' then row.rksj else n2.rksj end "); sb.append(" on create set n2.sfz=row.sfz,n2.name=row.name,n2.sex=row.sex,n2.rksj=row.rksj "); sb.append(" with n1,n2,row merge (n2)-[r:OWNPHONE]->(n1) "); neo4jJDBCHelper.executeQuery(sb.toString().replace("\\\\'", "\\'"), new QueryCallBack() { @Override public void process(ResultSet rs) throws Exception { } }); sb = null; }