首页 > 技术文章 > neo4j使用文档

naimao 2020-08-13 16:14 原文

概述

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 trycreate语句load csv语句neo4j-importBatchInserterbatch-importapoc
适用场景 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;
				}
 

  

推荐阅读