1概念

Neo4j是一个NosSql的图形数据库。这里的图不是图片,而是一种数据结构。以图的方式存储数据。

1.1 图论了解

众所周知,图论起源于一个非常经典的问题——柯尼斯堡(Konigsberg)七桥问题。1738年,瑞典数 学家欧拉( Leornhard Euler)解决了柯尼斯堡七桥问题。由此图论诞生,欧拉也成为图论的创始人。

neo4j笔记 - 图1

欧拉把问题的实质归于”一笔画”问题,即判断一个图是否能够遍历完所有的边(Edge)而没有重复,而柯 尼斯堡七桥问题则是一笔画问题的一个具体情境。欧拉证明这个问题不成立。

满足一笔画的图满足两个条件:

  • 图必须是一个完整图 (每个节点都有一条一条边相连)
  • 有零个或二个奇数点

图和节点:

图是一组节点和连接这些节点(边)的关系组成。

图形数据存储在节点和关系所在的属性上。属性是键值对表 示的数据。

在图形理论中,我们可以使用圆表示一个节点 并且可以向里面添加键值对形式的数据

节点关系:

  • 简单关系 neo4j笔记 - 图2
    此处在两个节点之间创建关系名称“跟随”。 这意味着Profile1 跟随 Profile2。

  • 复杂关系 neo4j笔记 - 图3
    这里节点用关系连接。 关系是单向或双向的。
    从XYZ到PQR的关系是单向关系。
    从ABC到XYZ的关系是双向关系。

图属性规则:

neo4j笔记 - 图4

  • 图表示节点,关系和属性中的数据
  • 节点和关系都包含属性
  • 关系连接节点
  • 属性是键值对
  • 节点用圆圈表示,关系用方向键表示。
  • 关系具有方向:单向和双向。
  • 每个关系包含“开始节点”或“从节点” 和 “到节点”或“结束节点”

1.2 知识图谱和图库

知识图谱:

一种基于图的数据结构,由节点(Node)和边(Edge)组成。其中节点即实体,由一个全局唯一的ID标示, 边就是关系用于连接两个节点。通俗地讲,知识图谱就是把所有不同种类的信息(Heterogeneous Information)连接在一起而得到的一个关系网络。知识图谱提供了从“关系”的角度去分析问题的能力。

互联网、大数据的背景下,谷歌、百度、搜狗等搜索引擎纷纷基于该背景,创建自己的知识图谱 Knowledge Graph(谷歌)、知心(百度)和知立方(搜狗),主要用于改进搜索质量。

图数据库:

一般情况下,我们使用数据库查找事物间的联系的时候,只需要短程关系的查询(两层以内的关联)。 当需要进行更长程的,更广范围的关系查询时,就需要图数据库的功能。

而随着社交、电商、金融、零 售、物联网等行业的快速发展,现实世界的事物之间织起了一张巨大复杂的关系网,传统数据库面对这 样复杂关系往往束手无策。因此,图数据库应运而生。

图数据库(Graph database)指的是以图数据结构的形式来存储和查询数据的数据库。

知识图谱中,知识的组织形式采用的就是图结构,所以非常适合用图库进行存储。

图形数据库的优势:
在需要表示多对多关系时,我们常常需要创建一个关联表来记录不同实体的多对多关系。如果两个实体 之间拥有多种关系,那么我们就需要在它们之间创建多个关联表。而在一个图形数据库中,我们只需要 标明两者之间存在着不同的关系。如果希望在两个结点集间建立双向关系,我们就需要为每个方向定义 一个关系。 也就是说,相对于关系型数据库中的各种关联表,图形数据库中的关系可以通过关系属性这 一功能来提供更为丰富的关系展现方式。因此相较于关系型数据库,图形数据库的用户在对现实进行抽 象时将拥有一个额外的武器,那就是丰富的关系。

  1. ![image-20201010140840504](assets/image-20201010140840504.png)

优势总结:

  • 性能上,对长程关系的查询速度快

  • 擅于发现隐藏的关系,例如通过判断图上两点之间有没有走的通的路径,就可以发现事物间的关联

1.3 什么是Neo4j

Neo4j是一个开源的 无Shcema的 基于java开发的 图形数据库,它将结构化数据存储在图中而不 是表中。它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎。程序数据是 在一个面向对象的、灵活的网络结构下,而不是严格、静态的表中,但可以享受到具备完全的事务 特性、企业级的数据库的所有好处。

1.4 Neo4j 模块

Neo4j 主要构建块 节点,属性,关系, 标签,数据浏览器

  • 节点
    节点是图表的基本单位。 它包含具有键值对的属性

  • 属性
    属性是用于描述图节点和关系的键值对
    Key =值 其中Key是一个字符串 值可以通过使用任何Neo4j数据类型来表示

  • 关系
    关系是图形数据库的另一个主要构建块。 它连接两个节点,如下所示。 neo4j笔记 - 图5
    这里Emp和Dept是两个不同的节点。 “WORKS_FOR”是Emp和Dept节点之间的关系。 因为它表示从Emp到Dept的箭头标记,那么这种关系描述的一样 Emp WORKS_FOR Dept 每个关系包含一个起始节点和一个结束节点。 这里“Emp”是一个起始节点。 “Dept”是端节点。 由于该关系箭头标记表示从“Emp”节点到“Dept”节点的关系,该关系被称为“进入关系”到“Dept”节点。 并且“外向关系”到“Emp”节点。 像节点一样,关系也可以包含属性作为键值对。

  • 标签
    Label将一个公共名称与一组节点或关系相关联。
    节点或关系可以包含一个或多个标签。
    我们可以为现有节点或关系创建新标签。
    我们可以从现有节点或关系中删除现有标签。 从前面的图中,我们可以观察到有两个节点。 左侧节点都有一个标签:“EMP”,而右侧节点都有一个标签:“Dept”。 这两个节点之间的关系,也有一个签:“WORKS_FOR”
    注: Neo4j将数据存储在节点或关系的属性中。

  • 数据浏览器

1.5 Neo4j应用场景

  • 社交媒体和社交网络
    当使用图形数据库为社交网络应用程序提供动力时,可以轻松利用社交关系或根据活动推断关系。 查询社区聚类分析,朋友的朋友推荐,影响者分析,共享和协作关系分析等 。

  • 推荐引擎和产品推荐系统
    图形驱动的推荐引擎通过实时利用多种连接,帮助公司个性化产品,内容和服务。 内容和媒体推荐,图形辅助搜索引擎,产品推荐,专业网络,社会推荐。

  • 身份和访问管理
    使用图形数据库进行身份和访问管理时,可以快速有效地跟踪用户,资产,关系和授权。 查询访问管理,资产来源,数据所有权,身份管理,互连组织,主数据,资源授权 。

  • 金融反欺诈多维关联分析场景
    通过图分析可以清楚地知道洗钱网络及相关嫌疑,例如对用户所使用的帐号、发生交易时的IP地址、 MAC地址、手机IMEI号等进行关联分析。

2 安装

2.1 linux

(1). 下载到本地然后利用lrzsz或者ftp工具上传

或者 wget https://neo4j.com/artifact.php?name=neo4j-community-3.5.17-unix.tar.gz

(2). 解压 tar -xvf neo4j-community-3.5.17.tar

(3). 修改配置文件 neo4j.conf vi conf/neo4j.conf 主要是修改 允许远程访问的地址 把对应的注释打开即可 dbms.connectors.default_listen_address=0.0.0.0

(4).开放对应的访问端口 默认要开放7474 和 7687

  1. firewall-cmd --zone=public --add-port=7474/tcp --permanent
  2. firewall-cmd --zone=public --add-port=7687/tcp --permanent
  3. systemctl reload firewalld

(5).启动

  1. ./bin/neo4j start

(6).使用浏览器 访问服务器上的 neo4j http://192.168.211.133:7474

默认的账号是 neo4j 密码 neo4j 这里第一次登录的话会要求修改密码

2.2 windows

(1).从https://neo4j.com/download-center/#community 下载最新的Neo4j Server安装文件 可以看到 neo4J 软件 exe 或 zip 格式的所有版本

(2).下载 Neo4j 3.5.17 (zip)

(3).解压

(4).修改配置文件 dbms.connectors.default_listen_address=0.0.0.0

(5) . 通过 neo4j.bat install-service 安装neo4j服务

注意的问题 如果是4.0 以及以上版本需要jdk11

修改文件 bin/neo4j.ps1 Import-Module “neo4j的主目录\bin\Neo4j-Management.psd1”

或者相对路径”.\Neo4j-Management.psd1”

(6).neo4j.bat启动 neo4j.bat start

(7).使用浏览器 访问服务器上的 neo4j http://127.0.0.1:7474
默认的账号是 neo4j 密码 neo4j 这里第一次登录的话会要求修改密码

3 Neo4j CQL

CQL代表Cypher查询语言。 像关系型数据库具有查询语言SQL,Neo4j使用CQL作为查询语言。

命令:

命令 作用
create 创建节点,关系和属性
match 检索有关节点,关系和属性数据
return 返回查询结果
where 提供条件过滤检索MATCH数据
delete 删除节点和关系
remove 删除节点和关系的属性 删除节点和关系的标签
set 添加或更新标签
order by 对结果排序
skip limit 分页
distinct 去重

基于这个图创建一个图谱

neo4j笔记 - 图6

3.1 Create

  1. CREATE (
  2. <node-name>:<label-name>
  3. [{
  4. <property1-name>:<property1-Value>
  5. ........
  6. <propertyn-name>:<propertyn-Value>
  7. }]
  8. )

[…]表示可选项

< node-name> 它是我们将要创建的节点名称。节点名称没什么含义,主要是标签名,标签名可以有多个

< label-name> 它是一个节点标签名称

< property1-name>…< propertynname> 属性是键值对。 定义将分配给创建节点的属性的名称

< property1-value>…< propertynvalue> 属性是键值对。 定义将分配给创建节点的属性的值

例:

  1. CREATE (person:Person)
  1. CREATE (person:Person {cid:1,name:"范闲",age:24,gender:0,character:"A",money:1000});
  2. CREATE (person:Person {cid:2,name:"林婉儿",age:20,gender:1,character:"B",money:800});
  3. CREATE (person:Person {cid:3,name:"庆帝",age:49,gender:0,character:"A",money:8900});

3.2 match return

  1. MATCH
  2. (
  3. <node-name>:<label-name>
  4. {}
  5. )
  6. RETURN
  7. <node-name>.<property1-name>,
  8. ...
  9. <node-name>.<propertyn-name>

< node-name> 它是我们将要创建的节点名称。

< label-name> 它是一个节点标签名称

{} 可以放匹配条件

< property1-name>…< propertynname> 属性是键值对。 定义将分配给创建节点的属性的名 称

  1. MATCH (person:Person) return person
  2. MATCH (person:Person) return person.name,person.age
  3. match(p:Person {name:"庆帝"}) return p

3.3 关系创建

两个节点之间可以创建多个关系,可以有属性,也可以没有属性。就像节点一样。属性是为了描述节点,节点关系的信息。

关系是有方向的。创建的时候不能同时存在两个方向的关系。比如

  1. create (p1)<-[rr:friend]->(p2)

但是 可以

  1. create (p1)-[rr:friend]->(p2)
  2. 或者
  3. create (p1)<-[rr:friend]-(p2)

使用现有节点创建没有属性的关系

  1. MATCH (<node1-name>:<node1-label-name>),(<node2-name>:<node2-label-name>)
  2. CREATE (<node1-name>)
  3. -[<relationship-name>:<relationship-label-name>]->
  4. (<node2-name>)

< noode1-name> 它用于创建关系的“From Node”的名称。

< node1-label-name> 它用于创建关系的“From Node”的标签名称。

< node2-name> 它用于创建关系的“To Node”的名称。

< node2-label-name> 它用于创建关系的“To Node”的标签名称。

< relationship-name> 这是一个关系的名称。

< relationship-label-name> 它是一个关系的标签名称。

  1. match(p1:Person{name:"北齐圣女海棠朵朵"}),(p2:Person{name:"北齐小皇帝战豆豆"})
  2. create(p1)<-[:Friend]->(p2)
  1. 创建关系
  2. match(person:Person {name:"范闲"}) ,(person2:Person {name:"林婉儿"})
  3. create(person)-[r:Couple]->(person2);
  1. 查询关系
  2. match p = (person:Person {name:"范闲"})-[r:Couple]->(person2:Person) return p
  3. match (p1:Person {name:"范闲"})-[r:Couple]-(p2:Person) return p1,p2
  4. match (p1:Person {name:"范闲"})-[r:Couple]-(p2:Person) return r

使用现有节点创建有属性的关系

  1. MATCH (<node1-label-name>:<node1-name>),(<node2-label-name>:<node2-name>)
  2. CREATE(<node1-label-name>)
  3. -[<relationship-label-name>:<relationship-name> {<define-properties-list>}]->
  4. (<node2-label-name>)
  5. RETURN <relationship-label-name>
  6. 其中<define-properties-list> 是分配给新创建关系的属性(名称 - 值对)的列表。
  7. {
  8. <property1-name>:<property1-value>,
  9. <property2-name>:<property2-value>,
  10. ...
  11. <propertyn-name>:<propertyn-value>
  12. }
  1. match(person:Person {name:"范闲"}),(person2:Person {name:"林婉儿"})
  2. create(person)-[r:Couple{mary_date:"1802-12-12",price:55000}]->(person2)
  3. return r;

也可以不返回。

使用新节点创建没有属性的关系

  1. CREATE
  2. (<node1-label-name>:<node1-name>)
  3. -[<relationship-label-name>:<relationship-name>]->
  4. (<node1-label-name>:<node1-name>)
  1. create(person1:Person {cid:4,name:"长公主",age:49,gender:1,character:"A",money:5000})
  2. -[r:Friend]->
  3. (person2:Person {cid:7,name:"九品射手燕小乙",age:48,gender:0,character:"B",money:1000})
  1. create(person1:Person{cid:8,name:"二皇子",age:21,gender:0,character:"B",money:900})
  2. -[r:Friend]->
  3. create(person2:Person{cid:9,name:"靖王世子",age:20,gender:0,character:"C",money:890})

使用新节点创建有属性的关系

  1. CREATE
  2. (<node1-label-name>:<node1-name>{<define-properties-list>})
  3. -[<relationship-label-name>:<relationship-name>{<define-properties-list>}]->
  4. (<node1-label-name>:<node1-name>{<define-properties-list>})
  1. create (person1:Person {cid:9,name:"靖王世子",age:23,gender:0,character:"A",money:3000})
  2. -[r:Friend {date:"1800-10-19"}]->
  3. (person2:Person {cid:8,name:"二皇子",age:24,gender:0,character:"B",money:6000})

3.4 关系和节点的属性可以使用的类型

  1. boolean 它用于表示布尔文字:true, false。

  2. byte 它用来表示8位整数。

  3. short 它用于表示16位整数。

  4. int 它用于表示32位整数。

  5. long 它用于表示64位整数。

  6. float 浮点数用于表示32位浮点数。

  7. double Double用于表示64位浮点数。

  8. char Char用于表示16位字符。

  9. String 字符串用于表示字符串。

3.5 CREATE创建多个标签

一个节点可以有多个标签。可以在创建的时候指定多个标签,也可以在后面添加或删除。

  1. CREATE (<node-name>:<label-name1>:<label-name2>.....:<label-namen>)
  2. 如:
  3. CREATE (person:Person:Beauty:Picture {cid:20,name:"小美女"})

3.6 Where

where 子句中可以像mysql中使用 and,or,not等条件连接符

还有 =,!=,<>,>,<等比较符

  1. MATCH (person:Person)
  2. WHERE person.name = '范闲' OR person.name = '靖王世子'
  3. RETURN person

可以只查询部分字段,并且给别名 使用as 就像mysql那样。必须要有as

  1. MATCH (person:Person)
  2. WHERE person.name = '范闲' OR person.name = '靖王世子'
  3. RETURN person.name as fullName,person.age

3.7 delete和remove

delete可以删除节点,以及节点之间的关系。如果节点之间存在关系,删除之前必须先删除节点之间的关系。

首先创建

  1. create(p1:Person {name:"r1"}) <-[rr:friend]- (p2:Person {name:"r2"})

然后删除关系:

  1. match(p1:Person)-[rr:friend]->(p2:Person) delete rr

然后删除节点

  1. match(p1:Person) where p1.name='r1' or p1.name='r2' delete p1

remove是删除节点或关系的标签,节点或关系的属性。

首先创建

  1. create(p:Person:beauty:small {cid:60,name:"小美女",age:18})

然后删除age属性

  1. match(p:Person)where p.name="小美女" remove p.age

删除small标签

  1. match(b:beauty {name:"小美女"})remove b:small
  1. match(b:beauty {name:"小美女"})remove b:beauty

3.8 set

  • 向现有节点或关系添加新属性

  • 更新属性值

  1. MATCH (p:Person {cid:1})
  2. SET p.money = 3456,p.age=25

如果有这个属性就更新属性值。如果没有就添加属性。相当于upInsert

3.9 order by ,skip,limit,distinct

order by根据属性进行排序必须放在return后面

默认是asc升序

  1. match(p:Person) return p.cid,p.name,p.age order by p.money desc

neo4j笔记 - 图7

分页

  1. match(p:Person) return p.cid,p.name,p.age order by p.money desc skip 2 limit 2

neo4j笔记 - 图8

去重

  1. match(p:Person) return distinct(p.age)
  1. #这种是无效的
  2. match(p:Person) return distinct(p.age),p.name

neo4j笔记 - 图9

3.10 in

neo4j也可以使用in操作符

  1. match (p:Person) where p.name in ["范闲","林婉儿"] return p

3.11 关系查询

  1. MATCH p=()-[r:Friend]->() RETURN p LIMIT 25

查询多个关系:

  1. MATCH p=()-[r:Friend|Couple]->() RETURN p LIMIT 25

查询范闲的朋友

  1. MATCH p=({name:"范闲"})-[r:Friend]->() return p

4 CQL高级

4.1 字符串函数

我使用的是4.1.3版本的neo4j。之前的版本是upper,lower。

函数名不区分大小写

toUpper它用于将所有字母更改为大写字母。

toLower它用于将所有字母改为小写字母。

substring它用于获取给定String的子字符串。

replace它用于替换一个字符串的子字符串。

  1. MATCH (p:Person) RETURN ID(p),toLower(p.character)
  1. match(p:Person) return p.character,lower(p.character),p.name,substring(p.name,2),replace(p.name,"子","z
  2. i")

4.2 聚合函数

count,max,min,sum,avg

count不计算为null的列。如果使用p.property的时候

  1. MATCH (p:Person)
  2. RETURN MAX(p.money),SUM(p.money),min(p.money),count(*),count(p),count(p.money)
  1. MATCH (p:Person)
  2. where p.money > 890 RETURN MAX(p.money),SUM(p.money),min(p.money)

4.3 关系函数

neo4j笔记 - 图10

不区分大小写。

STARTNODE 它用于知道关系的开始节点。

ENDNODE 它用于知道关系的结束节点。

ID 它用于知道关系的ID。

TYPE 它用于知道字符串表示中的一个关系的TYPE。比如Couple,Friend

  1. 查询和林婉儿是夫妻关系的头节点。
  2. match p = (:Person {name:"林婉儿"})-[r:Couple]-(:Person) RETURN STARTNODE(r)

查出来是name=范闲节点

因为关系表达式使用的是 -[r:Couple]-所以是不区分方向的。若要是以林婉儿为开始节点,则可以-[r:Couple]->。

  1. 查询和林婉儿是夫妻关系的头节点,并且林婉儿是开始节点
  2. match p = (:Person {name:"林婉儿"})-[r:Couple]->(:Person)RETURN STARTNODE(r)

这样是没结果的。

4.4 shorthestPath 函数返回最短的path

语法:

  1. MATCH p=shortestPath( (node1)-[*]-(node2) )
  2. RETURN length(p), nodes(p)

不要这个nodes函数也可以

一个节点到另一个节点的最短路径。

neo4j笔记 - 图11

如图,范闲到长公主的最短路径为1。

范闲到二皇子的最短路径为2。

那么九品射手到靖王世子的最短路径是多少呢?

我们可以看一下从 九品射手到晋王世子可以通过那些途径认识靖王世子。 这个*号必须指定

  1. match p=(:Person {name:"九品射手燕小乙"})-[*]-(:Person {name:"靖王世子"})return p

neo4j笔记 - 图12

然后使用shortestPath

  1. match p=shortestpath((:Person {name:"九品射手燕小乙"})-[*]-(:Person {name:"靖王世子"}))return p

neo4j笔记 - 图13

4.5 CQL多深度关系节点

with

查询三层级关系节点如下:with可以将前面查询结果作为后面查询条件

比如我们查询两层关系节点:

关系里面可以指定是什么关系进行过滤,也可以不指定

关系可以是无向的也可以是有向的。

  1. match(p:Person {name:"范闲"})-[re]->(p2:Person) return p,p2
  2. #或者这样写
  3. match m=(p:Person {name:"范闲"})-[re]->(p2:Person) return m
  4. match m=(:Person {name:"范闲"})-[re]->(:Person) return m

neo4j笔记 - 图14

  1. match(p:Person {name:"范闲"})-[re:Couple]->(p2:Person) return p,p2

neo4j笔记 - 图15

with可以将前面查询结果作为后面查询条件。

比如可以通过第二层的关系认识第三层的那些人。

p2就是第二层所认识的人,然后拿p2作为查询条件。相当于mysql中的一个子查询吧。

  1. match(p1:Person {name:"范闲"})-[re]->(p2:Person) with p1,re,p2 match(p2)-[re2]-(p3) return p1,p2,p3

neo4j笔记 - 图16

如果只通过儿子关系去认识第二层的朋友

  1. match(p1:Person {name:"范闲"})-[re:Son]->(p2:Person) with p1,re,p2 match(p2:Person)-[re2:Friend]-(p3:Person) return p1,p2,p3

neo4j笔记 - 图17

林婉儿认识第三层的人

  1. match(p1:Person {name:"林婉儿"})-[re]->(p2:Person) with p1,re,p2 match(p2:Person)-[re2]-(p3:Person) return p1p2,p3

neo4j笔记 - 图18

只认识第二层的朋友

  1. match(p1:Person {name:"林婉儿"})-[re]->(p2:Person) with p1,re,p2 match(p2:Person)-[re2:Friend]-(p3:Person) return p1,p2,p3

neo4j笔记 - 图19

直接拼接

前面是使用with。这里不适用with直接拼接。其实效果是一样的。

  1. match(p1:Person {name:"林婉儿"})-[re]->(p2:Person)-[re2:Friend]-(p3:Person) return p1,p2,p3

neo4j笔记 - 图20

  1. match(p1:Person {name:"林婉儿"})-[re]-(p2:Person)-[re2:Friend]-(p3:Person) return p1,p2,p3

neo4j笔记 - 图21

深度运算符

前面两种使用with和直接拼接太繁琐了。

可以使用深度运算符,但就是无法控制准确的精度。

关系标签Type可以省略。

  1. 可变数量的关系->节点可以使用-[:TYPE*minHops..maxHops]-。
  1. match data=(na:Person{name:"范闲"})-[*1..2]-(nb:Person) return data

neo4j笔记 - 图22

  1. match data=(na:Person{name:"范闲"})-[*1..3]-(nb:Person) return data

neo4j笔记 - 图23

  1. match data=(na:Person{name:"范闲"})-[:Son*1..3]-(nb:Person) return data

neo4j笔记 - 图24

5 事务

Neo4j支持ACID特性。

(1)所有对Neo4j数据库的数据修改操作都必须封装在事务里。

(2)默认的isolation level是READ_COMMITTED。

(3)死锁保护已经内置到核心事务管理 。 (Neo4j会在死锁发生之前检测死锁并抛出异常。在异常抛出之前,事务会被标志为回滚。当事务结束时,事务会释放它所持有的锁,则该事务的锁所引起的死锁也就是解 除,其他事务就可以继续执行。当用户需要时,抛出异常的事务可以尝试重新执行)

(4)除特别说明,Neo4j的API的操作都是线程安全的,Neo4j数据库的操作也就没有必要使用外部的同步方 法。

6 索引

官方文档:【https://neo4j.com/docs/cypher-manual/4.2/administration/indexes-for-search-performance/#administration-indexes-create-a-single-property-index】

Neo4j CQL支持节点或关系属性上的索引,以提高应用程序的性能。

可以为具有相同标签名称的属性上创建索引。

可以在MATCH或WHERE等运算符上使用这些索引列来改进CQL 的执行。

Neo4j的索引也是使用btree来实现的。

单一索引:

  1. CREATE INDEX [indexName] ON :Label(property)
  2. CREATE INDEX idx_p_name ON :Person(name)
  1. #包含就像mysql like会使用索引
  2. match(p:Person) where p.name contains "皇" return p
  3. #左匹配 会使用索引
  4. match(p:Person) where p.name starts with "北" return p
  5. #右匹配会使用索引
  6. match(p:Person) where p.name ends with "闲" return p

复合索引:

  1. CREATE INDEX ON :Label(property1,property2)
  2. CREATE INDEX ON :Person(age, gender)

全文索引:

neo4j的全文搜索使用的是lucene.

call db.index.fulltext.listAvailableAnalyzers命令可以查看有那些解析器,没有中文的

之前的常规模式索引只能对字符串进行精确匹配或者前后缀索引(starts with,ends with,contains),全文 索引将标记化索引字符串值,因此它可以匹配字符串中任何位置的术语。索引字符串如何被标记化并分 解为术语,取决于配置全文模式索引的分析器。索引是通过属性来创建,便于快速查找节点或者关系。

创建和配置全文模式索引

使用db.index.fulltext.createNodeIndex和db.index.fulltext.createRelationshipIndex创建全文模式索 引。在创建索引时,每个索引必须为每个索引指定一个唯一的名称,用于在查询或删除索引时引用相关 的特定索引。然后,全文模式索引分别应用于标签列表或关系类型列表,分别用于节点和关系索引,然 后应用于属性名称列表。

  1. call db.index.fulltext.createNodeIndex("索引名",[Label,Label],[属性,属性])
  2. call db.index.fulltext.createNodeIndex("nameAndDescription",["Person"],["name",
  3. "description"])
  4. call db.index.fulltext.queryNodes("nameAndDescription", "范闲") YIELD node, score
  5. RETURN node.name, node.description, score
  1. call db.index.fulltext.queryNodes("索引名", "query") YIELD node, score
  2. RETURN node.name, node.description, score

query 也会进行分词 类似于es的 string查询。score是打分情况

查看和删除索引:

  1. call db.indexes 或者 :schema 或者call db.indexes()
  2. DROP INDEX ON :Person(name)
  3. DROP INDEX ON :Person(age, gender)
  4. call db.index.fulltext.drop("nameAndDescription")

唯一约束:

避免重复记录。 强制执行数据完整性规则。

创建唯一约束的时候也会像mysql那样创建索引。所以如果一个属性创建了索引,再创建唯一约束的时候就会报异常。

  1. CREATE CONSTRAINT ON (变量:<label_name>) ASSERT 变量.<property_name> IS UNIQUE
  2. CREATE CONSTRAINT ON (person:Person) ASSERT person.name IS UNIQUE

删除唯一约束

  1. DROP CONSTRAINT ON (cc:Person) ASSERT cc.name IS UNIQUE

属性存在约束(企业版可用)

  1. CREATE CONSTRAINT ON (p:Person) ASSERT exists(p.name)

查看约束

  1. call db.constraints
  2. 或者 :schema

7 备份与恢复

我们使用的是社区版,备份和恢复的时候需要先关闭neo4j服务。企业版可以服务运行的时候备份和恢复。

备份:

  • 停止服务
  1. ./bin/neo4j stop
  • 备份
  1. ./bin/neo4j-admin dump --database=graph.db --to=/root/qyn.dump

我用的是4.1.3版本的datbase=neo4j

  • 恢复 如果服务在运行,也需要先停止
    我用的是4.1.3版本的datbase=neo4j
  1. ./bin/neo4j-admin load --database=graph.db --from=/root/qyn.dump --force
  • 启动
  1. ./bin/neo4j start

linux系统备份出现警告 dump的时候

WARNING: Max 1024 open files allowed, minimum of 40000 recommended. See the Neo4j manual

修改 /etc/security/limits.conf配置文件

在最后面加上

  1. * soft nofile 65535
  2. * hard nofile 65535

然后重启linux服务就可以了。

8 优化

8.1 增加服务器内存

  1. # java heap 初始值
  2. dbms.memory.heap.initial_size=1g
  3. # java heap 最大值,一般不要超过可用物理内存的80%
  4. dbms.memory.heap.max_size=8g
  5. # pagecache大小,官方建议设为:(总内存dbms.memory.heap.max_size)/2,
  6. dbms.memory.pagecache.size=4g

8.2 数据预热

neo4j刚启动数据是冷的。可以执行以下cql语句进行数据预热。

  1. MATCH (n)
  2. OPTIONAL MATCH (n)-[r]->()
  3. RETURN count(n.name) + count(r);

8.3 查看执行计划

我们在查询cql前面加上explain或者profile看着执行计划。profile更详细一些,建议使用profile。

  1. profile match (p:Person) where p.name in ["范闲","林婉儿"] return p

neo4j笔记 - 图25

主要看三个指标:

  • 是否使用索引
  • estimated rows: 需要被扫描行数的预估值
  • 数的预估值 dbhits: 实际运行结果的命中绩效

后面两项数值越小越好。

9 Neo4j程序访问

9.1 数据访问

有两种方式:

  • 嵌入式数据库,不需要单独将neo4j单独部署,直接嵌入程序使用
  • 服务器模式(通过REST的访问)

它是由应用程序的性质(neo4j是独立服务器 还是和程序在一起),性能,监控和数据安全性来决定架构 选择。

嵌入式数据库:

  • 使用Java作为我们项目的编程语言时
  • 应用程序是独立的
  • 程序追求很高的性能

服务器模式:

Neo4j Server是相互操作性,安全性和监控的最佳选择。 实际上,REST接口允许所有现代平台和编程 语言与它进行互操作。 此外,作为独立应用程序,它比嵌入式配置更安全(客户端中的潜在故障不会影响服务器),并且更易于监控。 如果我们选择使用这种模式,我们的应用程序将充当Neo4j服务器的客 户端。要连接到Neo4j服务器,可以使用任何编程语言的REST 访问数据库。

9.2 嵌入方式访问

引入依赖:

  1. <dependency>
  2. <groupId>org.neo4j</groupId>
  3. <artifactId>neo4j</artifactId>
  4. <version>4.1.0</version>
  5. </dependency>

我这里使用的是4版本以上的,索引使用api方式有点不同

官方文档:【https://neo4j.com/docs/java-reference/current/java-embedded/hello-world/】

  1. public class EmbeddedNeo4jAdd {
  2. private final static File dbDir = new File("embedded-neo4j/src/neo4j");
  3. private final static String DEFAULT_DB_NAME = "neo4j";
  4. public static void main(String[] args) {
  5. DatabaseManagementService databaseManagementService = new DatabaseManagementServiceBuilder(dbDir).build();
  6. GraphDatabaseService graphDatabaseService = databaseManagementService.database(DEFAULT_DB_NAME);
  7. try(Transaction tx = graphDatabaseService.beginTx()){
  8. Node node1 = tx.createNode();
  9. node1.addLabel(new MyLabel("Person"));
  10. node1.setProperty("cid",1);
  11. node1.setProperty("name","张三");
  12. node1.setProperty("age",23);
  13. node1.setProperty("money",8900);
  14. String cql = "create(:Person {cid:2,name:'李四',age:25,money:11000})";
  15. tx.execute(cql);
  16. tx.commit();
  17. }
  18. }
  19. }
  1. public class EmbeddedNeo4jQuery {
  2. private final static File dbDir = new File("embedded-neo4j/src/neo4j");
  3. private final static String DEFAULT_DB_NAME = "neo4j";
  4. public static void main(String[] args) {
  5. DatabaseManagementService databaseManagementService = new DatabaseManagementServiceBuilder(dbDir).build();
  6. GraphDatabaseService graphDatabaseService = databaseManagementService.database(DEFAULT_DB_NAME);
  7. try(Transaction tx = graphDatabaseService.beginTx()){
  8. String cql = "match(pp:Person) where pp.age < $age return pp";
  9. Map<String,Object> arg = new HashMap<>(2);
  10. arg.put("age",30);
  11. Result result = tx.execute(cql,arg);
  12. while (result.hasNext()) {
  13. Map<String, Object> next = result.next();
  14. for (Map.Entry<String, Object> e : next.entrySet()) {
  15. Node nd = (Node) e.getValue();
  16. System.out.println(e.getKey()+"_"+nd.getProperty("name")+":"+nd.getProperty("money"));
  17. }
  18. }
  19. tx.commit();
  20. }
  21. }
  22. }

4以下的版本:

  1. public class EmbeddedNeo4jAdd {
  2. private static final File databaseDirectory = new File( "target/graph.db" );
  3. public static void main(String[] args) {
  4. GraphDatabaseService graphDb = new
  5. GraphDatabaseFactory().newEmbeddedDatabase(databaseDirectory);
  6. System.out.println("Database Load!");
  7. Transaction tx = graphDb.beginTx();
  8. Node n1 = graphDb.createNode();
  9. n1.setProperty("name", "张三");
  10. n1.setProperty("character", "A");
  11. n1.setProperty("gender",1);
  12. n1.setProperty("money", 1101);
  13. n1.addLabel(new Label() {
  14. @Override
  15. public String name() {
  16. return "Person";
  17. }
  18. });
  19. String cql = "CREATE (p:Person{name:'李四',character:'B',gender:1,money:21000})";
  20. graphDb.execute(cql);
  21. tx.success();
  22. tx.close();
  23. System.out.println("Database Shutdown!");
  24. graphDb.shutdown();
  25. }
  26. }
  1. public class EmbeddedNeo4jQueryAll {
  2. private static final File databaseDirectory = new File( "target/graph.db" );
  3. public static void main(String[] args) {
  4. GraphDatabaseService graphDb = new
  5. GraphDatabaseFactory().newEmbeddedDatabase(databaseDirectory);
  6. System.out.println("Database Load!");
  7. String cql = "MATCH (a:Person) where a.money < $money return a";
  8. Map<String, Object> paramerters = new HashMap<String, Object>();
  9. paramerters.put("money", 25000);
  10. Transaction tx = graphDb.beginTx();
  11. Result result = graphDb.execute(cql,paramerters);
  12. while (result.hasNext()) {
  13. Map<String, Object> row = result.next();
  14. for (String key : result.columns()) {
  15. Node nd = (Node) row.get(key);
  16. System.out.printf("%s = %s:%s%n", key,
  17. nd.getProperty("name"),nd.getProperty("money"));
  18. }
  19. }
  20. tx.success();
  21. tx.close();
  22. System.out.println("Database Shutdown!");
  23. graphDb.shutdown();
  24. }
  25. }

9.3 服务器模式

引入依赖:

  1. <dependency>
  2. <groupId>org.neo4j</groupId>
  3. <artifactId>neo4j-ogm-bolt-driver</artifactId>
  4. <version>3.2.17</version>
  5. </dependency>

demo:

  1. import org.neo4j.driver.*;
  2. import java.util.Map;
  3. import static org.neo4j.driver.Values.parameters;
  4. public class UseNeo4jDriver {
  5. public static void main(String[] args) {
  6. try(Driver driver = GraphDatabase.driver("bolt://127.0.0.1:7687",AuthTokens.basic("neo4j","123456"));
  7. Session session = driver.session();){
  8. query1(session);
  9. query2(session);
  10. }
  11. }
  12. private static void query1(Session session){
  13. String cql = "match(p:Person) return p.name as name,p.money as money,p.age as age";
  14. Result result = session.run(cql);
  15. while (result.hasNext()) {
  16. Record next = result.next();
  17. Map<String, Object> v = next.asMap();
  18. for (Map.Entry<String, Object> e : v.entrySet()) {
  19. System.out.print(e.getKey()+":"+e.getValue());
  20. }
  21. System.out.println();
  22. //System.out.println("name:"+next.get("name").asString()+",money:"+next.get("money").asInt()+",age:"+next.get("age").asInt());
  23. }
  24. }
  25. private static void query2(Session session){
  26. System.out.println("--------------分割线-------------");
  27. String cql = "match p=(p1:Person {name:$startName})-[*1..4]-(p2:Person {name:$endName}) return p";
  28. Result result = session.run(cql,parameters("startName","靖王世子","endName","九品射手燕小乙"));
  29. while(result.hasNext()){
  30. Record next = result.next();
  31. System.out.println(next);
  32. }
  33. }
  34. }

运行结果:

  1. name:范闲money:1000age:24
  2. name:林婉儿money:800age:20
  3. name:庆帝money:8900age:49
  4. name:二皇子money:900age:21
  5. name:靖王世子money:890age:20
  6. name:小美女money:nullage:null
  7. name:长公主money:1000age:43
  8. name:九品射手燕小乙money:700age:40
  9. name:北齐圣女海棠朵朵money:1100age:20
  10. --------------分割线-------------
  11. Record<{p: path[(4)<-[2:Friend]-(3), (3)-[3:Son]->(2), (2)-[5:Brother]->(6), (6)-[7:Friend]->(7)]}>

9.4 SpringBoot整合Neo4j

参考官方文档:【https://docs.spring.io/spring-data/neo4j/docs/5.3.4.RELEASE/reference/html/#reference】

配置文件:

  1. spring:
  2. data:
  3. neo4j:
  4. password: 123456
  5. username: neo4j
  6. uri: bolt://127.0.0.1:7687
  7. #uri: http:/127.0.0.1:7474
  8. #file: springboot-neo4j/src/neo4j #嵌入式

repository:

我这里的neo4j的服务版本为4.1.3所以只支持neo4j笔记 - 图26param方式

  1. public interface PersonRepository extends Neo4jRepository<Person,Long> {
  2. /**
  3. * 根据姓名删除 spring自动生成
  4. * @param name
  5. */
  6. void deleteByName(String name);
  7. /**
  8. * 根据姓名查询 spring自动生成
  9. * @param name
  10. * @return
  11. */
  12. Person queryByName(String name);
  13. /**
  14. * 根据姓名列表查询
  15. * @param names
  16. * @return
  17. */
  18. @Query("match(p:Person) where p.name in $names return p")
  19. List<Person> queryByNames(@Param("names") List<String> names);
  20. /**
  21. * 根据姓名查询 spring自动生成
  22. * @param names
  23. * @return
  24. */
  25. List<Person> queryByNameIn(List<String> names);
  26. /**
  27. * 查询最短路径
  28. * @param startName
  29. * @param endName
  30. * @return
  31. */
  32. @Query("match p=shortestPath((:Person {name:$startName})-[*1..4]-(:Person{name:$endName})) return p")
  33. List<Person> shortestPath(@Param("startName") String startName,@Param("endName") String endName);
  34. /**
  35. * 创建节点关系的时候 关系的label使用动态参数$relationLabel 会报错(官方给的文档也不能,必须指定),写死就没事,关系的属性值可以使用map传参
  36. * 实际使用的时候可以根据参数拼接cql
  37. *
  38. * @param startName
  39. * @param endName
  40. * @param relationLabel
  41. * @param relationProperties
  42. */
  43. @Query("match(p1:Person {name:$startName}),(p2:Person {name:$endName}) create (p1)-[:Friend $relationProperties]->(p2)")
  44. void buildRelationshipWithProperties(@Param("startName") String startName, @Param("endName") String endName, @Param("relationLabel") String relationLabel, @Param("relationProperties") Map<String,Object> relationProperties);
  45. }

entity:

  1. @NodeEntity
  2. public class Person {
  3. @Id
  4. @GeneratedValue
  5. private Long id;
  6. private int cid;
  7. @Property
  8. private String name;
  9. private String character;
  10. private double money;
  11. private int gender;
  12. private int age;
  13. private String description;
  14. /**
  15. * type为关系类型 direction为关系方向
  16. */
  17. @Relationship(type = "Friend",direction = Relationship.OUTGOING)
  18. private Set<Person> relationships;
  19. }

启动测试类:

  1. @SpringBootApplication
  2. public class SpringBootNeo4jApp {
  3. public static void main(String[] args) {
  4. ConfigurableApplicationContext context = SpringApplication.run(SpringBootNeo4jApp.class, args);
  5. PersonRepository personRepository = context.getBean(PersonRepository.class);
  6. /*queryAll(personRepository);
  7. System.out.println("----------分割线------------");
  8. queryByName(personRepository);
  9. System.out.println("----------分割线------------");
  10. queryByNames(personRepository);
  11. System.out.println("----------分割线------------");
  12. queryByNames2(personRepository);
  13. System.out.println("----------分割线------------");
  14. shortestPath(personRepository);
  15. System.out.println("----------分割线------------");*/
  16. buildRelationship(personRepository);
  17. }
  18. private static void queryAll(PersonRepository personRepository){
  19. //查询所有节点
  20. Iterable<Person> all = personRepository.findAll();
  21. all.forEach(System.out::println);
  22. }
  23. private static void queryByName(PersonRepository personRepository){
  24. Person person = personRepository.queryByName("范闲");
  25. System.out.println(person);
  26. }
  27. private static void queryByNames(PersonRepository personRepository){
  28. List<String> names = Arrays.asList("王启年","范闲","林婉儿");
  29. List<Person> people = personRepository.queryByNames(names);
  30. System.out.println(people);
  31. }
  32. private static void queryByNames2(PersonRepository personRepository){
  33. List<String> names = Arrays.asList("庆帝","范闲","林婉儿");
  34. List<Person> people = personRepository.queryByNameIn(names);
  35. System.out.println(people);
  36. }
  37. private static void shortestPath(PersonRepository personRepository){
  38. List<Person> people = personRepository.shortestPath("九品射手燕小乙", "靖王世子");
  39. System.out.println(people);
  40. }
  41. private static void save(PersonRepository personRepository){
  42. //新增节点
  43. Person person = new Person();
  44. person.setName("王启年");
  45. person.setAge(30);
  46. person.setCharacter("C");
  47. person.setGender(0);
  48. person.setMoney(1300);
  49. person.setCid(10);
  50. personRepository.save(person);
  51. }
  52. private static void buildRelationship(PersonRepository personRepository){
  53. Map<String,Object> properties = new HashMap<>();
  54. properties.put("date", "1800-12-09");
  55. personRepository.buildRelationshipWithProperties("范闲","王启年","Friend",properties);
  56. }
  57. private static void createNode(PersonRepository personRepository){
  58. //新增 张三,李四节点 并建立朋友关系
  59. Person person = new Person();
  60. person.setName("张三");
  61. person.setAge(30);
  62. person.setCharacter("C");
  63. person.setGender(0);
  64. person.setMoney(1300);
  65. person.setCid(10);
  66. //新增节点
  67. Person person2 = new Person();
  68. person2.setName("李四");
  69. person2.setAge(43);
  70. person2.setCharacter("B");
  71. person2.setGender(0);
  72. person2.setMoney(2300);
  73. person2.setCid(10);
  74. Set<Person> relation = new HashSet<>(2);
  75. relation.add(person2);
  76. person.setRelationships(relation);
  77. personRepository.save(person);
  78. }
  79. }