wget -c https://go.neo4j.com/download-thanks.html?edition=community&release=4.4.8&flavour=unix
tar xzf neo4j-community-4.4.8-unix.tar.gz
cd neo4j-community-4.4.8
vim conf/neo4j.conf
dbms.directories.data=/data/neo4j/data
dbms.directories.logs=/data/neo4j/logs
dbms.default_listen_address=0.0.0.0
# Notice JDK11
export JAVA_HOME=/usr/local/jdk-11.0.14
export NEO4J_HOME=/usr/local/neo4j-community-4.4.8
./bin/neo4j
1.2 基本概念
1.2.2 节点标签
在Neo4j中, 节点在默认情况下是没有类型的。确定领域模型中每一个节点应如何描述是开发者应考虑的问题。
每一个节点可以有一个或多个文字描述, 它称为节点标签。具有同样标签的节点用一种专用的方式存储, 因此它们能分为一组并可以一起使用。
标签是Neo4j的一个很好的补充, 不仅仅是一个分类的策略。因为节点可以有多个标签, 所以可以创建标签把经常一起使用的节点分组(即使不同的类型也可以), 从而不必使用属性。
1.2.2 节点(Node)
节点是指一个实实在在的对象, 这个对象可以有好多的标签, 表示对象的种类, 也可以有好多的属性, 描述其特征, 节点与节点之间还可以形成多个有方向(或者没有方向)的关系。
1.2.3 关系(Relationship)
用来描述节点与节点之间的关系, 这也是图数据与与普通数据库最大的区别, 正是因为有这些关系的存在, 才可以描述那些我们普通行列数据库所很难表示的网状关系, 比如我们复杂的人际关系网, 所谓的六度理论, 就可以很方便的用图数据库进行模拟, 比如我们大脑神经元之间的连接方式, 都是一张复杂的网。
有一点需要重点注意, 关系可以拥有属性。Relationship types 使用不常见的字符: CREATE (rob)-[:`TYPE WITH SPACE`]->(charlie)
1.2.4 属性(Property)
描述节点的特性, 采用的是Key-Value结构, 可以随意设定来描述节点的特征。
1.2.5 数据模型的类型
Neo4j是一个无架构数据库。在开始添加数据之前, 你并不需要定义表和关系。一个节点可以具有你喜欢的任何属性, 任何节点都可以与其他任何节点建立关系。Neo4j数据库中的数据模型隐含在它存储的数据中, 而不是明确地将数据模型定义为数据库本身的一个部分。它是对你想要存入数据库的数据的一个描述, 而不是数据库的一系列方法来限制将要存储的内容。
由于Neo4j的数据建模是描述性的, 而不是规定性的, 因此可以很容易对数据库将要存储的数据进行一致性的描述, 这样就可以以期望的方式构架查询, 并以相似的方式来描述相似的实体。
属性是Neo4j数据模型的重要组成部分, 而且这也是它不同于关系数据库模型的重要特征。在Neo4j中, 节点和关系都可以具有任意数量的属性, 这是非常重要的键/值对。它们通常用来存储节点和关系的特定属性。
2. Cypher
Cypher是对图形的声明查询语言, 使用图形模式匹配作为主要的机制作图形数据选择(包括只读和变更操作)。Cypher的声明模式匹配性质意味着可以通过描述想从它那里得到什么查询图形数据。
Cypher并不是唯一的图形查询语言, 另一个常用的图形查询语言是Gremlin, Neo4j也支持Gremlin。
Cypher和Gremlin的主要区别是它们的性质——Gremlin是命令式语言, 用户需要描述如何遍历图形, Cypher是声明性语言, 它使用模式匹配描述需要哪一部分图形数据。这就使得Cypher易读且易于理解。Gremlin可以在不同的图形数据库中移植, 而Cypher是由Neo4j团队开发, 并仅应用于Neo4j图形数据库。
2.1 关键字
Example
DELETE
删除节点和关系
MATCH (:god {name:"neptune"})-[r:father]->(:titan {name: "saturn"}) DELETE r;
DETACH DELETE
// God Graph
CREATE (:monster {name: "nemean"})<-[:battled {time: 1, place: "POINT (23.700001 38.099998)"}]-(hercules:demigod {name: "hercules", age: 30})-[:battled {place: "POINT (23.700001 38.099998)", time: 2}]->(:monster {name: "hydra"}),
(:location {name: "sky"})<-[:lives {reason: "loves fresh breezes"}]-(jupiter:god {name: "jupiter", age: 5000})<-[:father]-(hercules)-[:battled {time: 12, place: "POINT (22 39)"}]->(:monster {name: "cerberus"})<-[:pet]-(pluto:god {name: "pluto", age: 4000})-[:brother]->(neptune:god {name: "neptune", age: 4500})-[:brother]->(pluto),
(:location {name: "sea"})<-[:lives {reason: "loves waves"}]-(neptune)-[:brother]->(jupiter)<-[:brother]-(pluto)<-[:brother]-(jupiter)-[:father]->(:titan {name: "saturn", age: 10000}),
(jupiter)-[:brother]->(neptune),
(hercules)-[:mother]->(:human {name: "alcmene", age: 45}),
(pluto)-[:lives {reason: "no fear of death"}]->(:location {name: "tartarus"});
MATCH (a:demigod)-[:battled]->(b)
WHERE a.name = 'hercules'
RETURN a, b;
MERGE (a:god {name:"neptune"})
MERGE (b:titan {name: "saturn"})
CREATE (a)-[:father]->(b);
OPTIONAL MATCH (:demigod)-[]->(a:titan) RETURN
MATCH (:demigod)-[]->(a:titan) RETURN
(no changes, no records)
2.2 函数
Example
2.3 Cypher Shell
Param:
:param { "props" : { "name" : "Andres", "position" : "Developer" } }
3. 索引
在默认情况下, Neo4j引擎通过标签和属性查找节点是以蛮力执行的(通过循环查找所有具有给定标签的节点, 并比较需要的属性名和值)。但是, 如果对标签和属性定义了模式索引, Neo4j引擎将会使用更快的索引查找。
CREATE INDEX ON :LabelName(propertyName)
DROP INDEX ON :LabelName(propertyName)
CREATE CONSTRAINT ON (c:LabelName) ASSERT c.name IS UNIQUE: 唯一性限制
DROP CONSTRAINT ON (c:god) ASSERT c.name IS UNIQUE
:schema 查看所有索引和约束
因为应用开发者对使用的索引策略非常谨慎, 所以Neo4j并不自动更新由手动做的索引引起的图形数据库本身的数据改变。
一旦已经查找过一个索引, 就没有方法对这个已经存在的索引进行修改。解决的方法非常简单: 当处理Neo4j索引时, “先删除后添加”等于“更新”。
3.1 模式索引
在2.0版本中, Neo4j引进了模式索引的概念, 从概念上讲, 这与传统的关系数据库使用的索引处理方法很相似。模式索引与节点标签的概念紧密相关.
3.2 自动索引
要使用自动索引, 需要告诉Neo4j打开节点或关系, 或两者同时自动索引。但是, 仅仅是打开自动索引并不会引起任何变化。
在单机运行模式下打开自动索引, 需要以额外的属性修改配置文件。配置文件在$NEO4J_SERVER/conf/neo4j.properties文件夹中, 需要添加以下两行:
node_auto_indexing=true
relationship_auto_indexing=true
4. Neo4j 进阶
4.1 集群
Neo4j 集群使用主从复制实现高可用性和水平读扩展。
Neo4j 也允许通过从节点进行写入操作, 不过此时, 被写入的从节点会先将数据同步到主节点, 再将数据返回客户端。
ha.tx_push_factor = 2
ha.tx_push_strategy = ""
4.2 数据导入和导出
neo4j-admin import \
-multiline-fields**=true \
-database test1.db \
-nodes "/home/neo4j/node.csv" \
-relationships "/home/neo4j/relation.csv"
# –multiline-fields: 是否允许多行插入(即有些换行的数据也可读取)
# 数据库的名字, 必须是新的空的数据库
# 节点文件的位置
# 关系文件的位置
LOAD CSV WITH HEADERS FROM "/home/neo4j/locations.csv" AS line
WITH split(line.locations, ";") as locations, line.title as title
UNWIND locations AS location
MERGE (x:location {name:location})
MERGE (m:Movie {title:title})
MERGE (m)-[:FILMED_IN]->(x)
neo4j-admin dump
neo4j-admin load
4.3 清空 Neoj4
对于少量数据, 第一种方案是很方便的, 但是数据量大的时候很慢, 而且容易造成内存溢出, 毕竟是先查出来再删除, 这个时候就应该用第二种方案了, 方便, 快速。
方法一: Cypher 语法
match (n) detach delete n
方法一: 删除目录
删除 graph.db 目录
4.4 事务
Neo4j与其他一些没有结构化查询(NoSQL)技术的不同点在于, Neo4j是ACID完全兼容的(ACID是Atomic、Consistent、Isolated、Durable的首字母缩写——原子的、一致的、隔离的、持续的)。ACID完全兼容意味着Neo4j与使用传统的关系数据库一样有保证。
Neo4j事务的默认设置并不自动获取读锁。因此, 读的数据是节点、关系、索引实体最新提交的状态, 除非在它自己的事务中, 局域的修改(即使没提交)是可见的。
写锁则是在对任意图形资源试图做变更操作时自动获得, 在事务的持续时间内都有效。每一个事务确保在完成时自动释放所有的锁。这个默认行为与关系数据库中的读提交隔离级别非常相似, 隔离级别除去了潜在的脏读, 但是仍然允许幻像读和不可重复读的发生。
5. APOC
APOC - Awesome Procedures of Cypher 是Neo4j图数据库的扩展过程和函数库。
Neo4j图数据库扩展是基于Neo4j相关API和开发框架、使用Java开发的、部署在服务器端的过程和函数。这些过程和函数可以在Cypher中被调用, 就像存储过程可以在SQL中被调用一样, 因此我们有时也称这些过程为“存储过程”。
APOC提供了丰富的与查询执行、数据集成、数据库管理等相关的过程和函数。其他的扩展库例如ALGO则包含常用的图算法过程。
ALGO扩展包是二进制JAR文件, 可以直接下载、安装, 经过简单配置即可使用。
APOC的下载链接是: http://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/
在线文档: https://neo4j.com/docs/labs/apoc/current/
5.1 安装
安装完成、重新启动服务器后, 可以在Neo4j Browser中输入以下命令测试安装:
RETURN apoc.version()
5.1.1 自动安装
在Neo4j Desktop中安装APOC扩展包只需找到’Plugins’面板, 然后点击’Install’按钮。安装过程会自动下载最新版本的JAR文件、复制到特定本地目录下, 并修改配置文件。
5.1.2 手动安装
手动安装扩展包包括以下步骤:
根据Neo4j版本选择兼容的ALGO和APOC扩展包进行下载
将下载的JAR文件复制到<NEO4J_HOME>/plugins
目录下
打开<NEO4J_HOME>/conf/neo4j.conf
文件, 添加以下配置选项:
dbms.security.procedures.unrestricted=apoc.*
如果需要使用APOC的导入导出过程, 还需要添加下面的行:
apoc.export.file.enabled=true
apoc.import.file.enabled=true
重新启动Neo4j数据库服务。
5.1.2 在Docker部署的Neo4j实例上安装
如果使用Docker部署Neo4j服务器, 可以先将扩展包JAR文件下载存储在本地或网络存储的/plugins卷中, 在Docker实例启动时加载该卷。例子如下:
mkdir plugins
pushd plugins
wget https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/3.5/apoc-3.5-all.jar
docker run --rm -e NEO4J_AUTH=none -p 7474:7474 -v \
$PWD/plugins:/plugins -p 7687:7687 neo4j:3.5
5.2 主要功能
6. 常用语法
MERGE (a:User {uid:'6ce58d75129d1dbdc472349251936d35', name: 'Lisi'}) MERGE (b:Movie {id:'m10727641'}) CREATE (a)-[:RATING {rating:4}]->(b);
MERGE (a:User {uid:'6ce58d75129d1dbdc472349251936d35', name: 'Lisi'}) MERGE (b:Movie {id:'m1308767'}) CREATE (a)-[:RATING {rating:3}]->(b);
MATCH (u1:User {uid:'6ce58d75129d1dbdc472349251936d35'})-[r1:RATING]->(m1)-[r2:DIRECTED|STARRING|HAS_TAG]-(u2)-[r3:DIRECTED|STARRING|HAS_TAG]-(m2:Movie {id: 'm26336252'}) RETURN u1,u2, r1, r2,r3, m1, m2
MATCH (u1:User {uid:'6ce58d75129d1dbdc472349251936d35'})-[r1:RATING]->(m1)-[r2:DIRECTED|STARRING]-(u2)-[r3:DIRECTED|STARRING]->(m2) RETURN u1,u2, r1, r2,r3, m1, m2
MATCH (u1:User {uid:'6ce58d75129d1dbdc472349251936d35'})-[r1:RATING]->(m1)-[r2:DIRECTED|STARRING|HAS_TAG]-(u2)-[r3:DIRECTED|STARRING|HAS_TAG]-(m2:Movie {id: 'm3274505'}) RETURN u1,u2, r1, r2,r3, m1, m2
MATCH (a:Movie {id: "m1401533"}) SET a.score = 0;
MATCH (n:Movie) WHERE n.name=~'碟中谍.*' RETURN n
MATCH (n:Movie) WHERE n.name starts with '碟中谍' RETURN n
CALL apoc.periodic.iterate("
MATCH (e:Entity)
WHERE e.id STARTS WITH 'Q'
RETURN e
// Prepare a SparQL query
WITH 'SELECT * WHERE{ ?item rdfs:label ?name . filter (?item = wd:' + e.id + ') filter (lang(?name) = \\"en\\") ' +
'OPTIONAL {?item wdt:P31 [rdfs:label ?label] .filter(lang(?label)=\\"en\\")}}' AS sparql, e
// make a request to Wikidata
CALL apoc.load.jsonParams(
'https://query.wikidata.org/sparql?query=' +
sparql,
{ Accept: 'application/sparql-results+json'}, null)
YIELD value
UNWIND value['results']['bindings'] as row
SET e.wikipedia_name = row.name.value
WITH e, row.label.value AS label
MERGE (c:Class {id:label})
MERGE (e)-[:INSTANCE_OF]->(c)
RETURN distinct 'done'", {batchSize:1, retry:1})
UNWIND $data AS row
MERGE (h:Entity {id: CASE WHEN NOT row.head_span.id = 'id-less' THEN row.head_span.id ELSE row.head_span.text END})
ON CREATE SET h.text = row.head_span.text
MERGE (t:Entity {id: CASE WHEN NOT row.tail_span.id = 'id-less' THEN row.tail_span.id ELSE row.tail_span.text END})
ON CREATE SET t.text = row.tail_span.text
WITH row, h, t
CALL apoc.merge.relationship(h, toUpper(replace(row.relation,' ', '_')),
YIELD rel
RETURN distinct 'done' AS result;
使用LDBC SNB测试neo4j性能