Neo4j简介

Neo4j是由Neo4j公司于2007年开发的一款图数据库系统,目前也是全球市场份额最高、生态最完善的图数据库产品。与传统关系型数据库将数据存储在二维表中不同,Neo4j采用图存储结构,数据以“节点-关系-属性”的形式存储,配合专门优化的图遍历引擎,Neo4j能以毫秒级响应复杂的多跳关系查询。到目前为止,Neo4j已被广泛应用于社交关系、推荐系统、知识图谱、金融风控等需要深度挖掘数据关联价值的场景。Neo4j分为社区版和商业版,其中社区版采用GPLv3协议开源。

项目主页:https://neo4j.com

什么是图数据库

图数据库是一种专门用于存储和查询图结构数据的数据库,它的核心不是其实不是可视化一张图,而是维护“关系”。在现实世界中,绝大多数数据都不是孤立存在的,而是通过各种关系相互连接的,人与人之间的社交关系、商品与用户之间的购买关系、知识点之间的关联关系、网络设备之间的连接关系等等。传统的关系型数据库虽然可以通过外键来表示关系,但这种表示方式是间接的。当需要查询多跳关系时,比如“用户A的朋友的朋友的朋友”,关系型数据库需要执行多次JOIN操作,随着跳数的增加查询性能会急剧下降。而图数据库将关系作为一等公民,它直接存储实体之间的连接,查询多跳关系时只需要沿着关系进行遍历,性能随跳数增加基本保持线性增长。

Neo4j是原生图数据库的代表,这意味着它的存储引擎和查询引擎都是专门为图结构设计的,而非在关系型数据库或其他存储系统之上封装一层图查询接口。原生设计带来了极致的性能优势,同时也提供了更完善的图数据管理能力,包括事务支持、ACID保证、高可用集群等,使其能够胜任企业级的核心业务场景。

Neo4j与关系型数据库的区别理解

为了更直观的理解Neo4j究竟能解决什么问题,我们可以从建模方式、查询方式和性能侧重点三方面对比传统关系型数据库进行理解。

建模方式差异

关系型数据库采用“表-行-列”二维模型,所有实体和关系都必须被拆分成多个二维表来存储。实体之间的关系通过外键来间接表示,多对多关系则需要额外的中间表。关系型数据库通常需要进行范式化设计,以减少数据冗余。

而图数据库采用“节点-关系-属性”图模型,实体被表示为节点,实体之间的关系被表示为有向边,节点和关系都可以拥有属性。这种模型与现实世界的实体关系结构一致,建模过程更加直观自然,不需要进行复杂的范式转换。图数据库直接存储关系,虽然会有一定的数据冗余,但却换来了查询性能的极大提升。

这里我们以“用户-电影-评分”这个经典场景为例,对比两种数据库的建模方式。

关系型数据库建模需要3张表:用户表、电影表、评分表(中间表)。

用户表(user)
┌────┬────────┬─────┐
│ id │ name   │ age │
├────┼────────┼─────┤
│ 1  │ Alice  │ 25  │
│ 2  │ Bob    │ 30  │
└────┴────────┴─────┘

电影表(movie)
┌────┬────────────┬──────────────┐
│ id │ title      │ release_year │
├────┼────────────┼──────────────┤
│ 1  │ Inception  │ 2010         │
│ 2  │ The Matrix │ 1999         │
└────┴────────────┴──────────────┘

评分表(rating)
┌─────────┬──────────┬───────┬────────────┐
│ user_id │ movie_id │ score │ rated_at   │
├─────────┼──────────┼───────┼────────────┤
│ 1       │ 1        │ 5     │ 2023-01-01 │
│ 1       │ 2        │ 4     │ 2023-01-02 │
│ 2       │ 1        │ 4     │ 2023-01-03 │
└─────────┴──────────┴───────┴────────────┘

Neo4j图数据库建模只需要2种节点和1种关系,不需要中间表。

graph LR
    A[User: Alice<br />age: 25] -->|LIKED<br />score: 5<br />rated_at: 2023-01-01| B[Movie: Inception<br />release_year: 2010]
    A -->|LIKED<br />score: 4<br />rated_at: 2023-01-02| C[Movie: The Matrix<br />release_year: 1999]
    D[User: Bob<br />age: 30] -->|LIKED<br />score: 4<br />rated_at: 2023-01-03| B

查询方式差异

关系型数据库使用SQL作为查询语言,SQL是基于集合的声明式语言,擅长处理结构化数据的聚合和过滤,但在处理关系查询时需要使用JOIN操作。Neo4j使用Cypher作为查询语言,Cypher是专门为图数据设计的声明式查询语言,它使用类似ASCII艺术的语法来描述图模式,让开发者可以直观地表达“我想找什么样的节点和关系”。

我们以“查询Alice喜欢的所有电影”这个简单需求为例,对比两种查询语言。

SQL查询写法如下。

SELECT m.title
FROM user u
JOIN rating r ON u.id = r.user_id
JOIN movie m ON r.movie_id = m.id
WHERE u.name = 'Alice';

Cypher查询写法如下。

MATCH (u:User {name: 'Alice'})-[:LIKED]->(m:Movie)
RETURN m.title;

可以看到Cypher的语法更加直观,它直接描述了“用户Alice通过LIKED关系指向电影”这个图模式,不需要编写复杂的JOIN语句。尤其是当查询变得更复杂时,比如“查询Alice的朋友喜欢的但Alice自己没看过的电影”,SQL需要3次以上的JOIN,而Cypher只需要简单扩展图模式即可。

性能侧重点差异

关系型数据库的性能优势在于处理单表或少量表的聚合查询以及事务处理,但在处理多跳关系查询时,随着JOIN次数的增加,查询性能会呈指数级下降。而Neo4j的性能优势恰恰在于处理多跳关系查询。由于Neo4j中的关系是直接存储的,查询时只需要沿着关系进行遍历,性能随跳数增加基本保持线性增长。

“节点-关系-属性”模型

要熟练使用Neo4j,我们首先需要理解“节点-关系-属性”模型,这是图数据库与关系型数据库最本质的区别。

节点(Node)

节点是图数据库中的基本单元,用于表示现实世界中的实体,比如用户、电影、商品、知识点等。每个节点可以拥有多个标签(Label),标签用于对节点进行归类。一个节点可以有任意个标签,但通常我们会给每个节点至少一个标签来标识其类型。

节点还可以拥有多个属性(Property),属性是键值对,它用于存储节点的特征信息,比如用户的姓名、年龄,电影的标题、上映年份等。属性的类型可以是字符串、数字、布尔值、日期等基本类型,也可以是数组类型。

关系(Relationship)

关系用于表示两个节点之间的连接,这是图数据库的核心。关系必须是有向的,从一个起始节点指向一个结束节点,但查询时可以忽略方向。每个关系必须有且只有一个类型(Type),用于标识关系的种类,比如LIKEDFRIENDS_WITHPURCHASED等。

和节点一样,关系也可以拥有多个属性,用于存储关系的特征信息,比如评分关系的分数、评分时间,朋友关系的建立时间等。这也是图模型的一个重要优势,关系本身也可以携带信息,而在关系型数据库中,这些信息只能存储在中间表中。

属性(Property)

属性是节点和关系的特征描述,以键值对的形式存在。Neo4j支持多种属性类型,包括:

  • 数值类型:Integer、Float、Long、Double
  • 字符串类型:String
  • 布尔类型:Boolean
  • 日期时间类型:Date、Time、DateTime、LocalDateTime
  • 空间类型:Point
  • 数组类型:以上类型的数组

标签(Label)与关系类型(Relationship Type)

标签和关系类型是图数据库中的元数据,用于对节点和关系进行归类。一个节点可以有多个标签,比如一个用户节点可以同时有UserAdmin标签。两个节点之间可以有多个不同类型的关系,比如用户和电影之间可以有LIKEDWATCHEDRATED等多种关系。

“节点-关系-属性”模型例子

我们用一个例子来总结这套思维模型。下图中有3个节点,分别是2个User标签的节点和1个Product标签的节点,节点之间有3个关系,分别是FRIENDS_WITHPURCHASEDLIKED类型,每个节点和关系都有自己的属性。

graph LR
    A[User: Alice<br />age: 25<br />email: alice@example.com] -->|FRIENDS_WITH<br />since: 2022-01-01| B[User: Bob<br />age: 30<br />email: bob@example.com]
    A -->|PURCHASED<br />quantity: 2<br />purchased_at: 2023-01-05| C[Product: iPhone 15<br />price: 7999<br />category: Electronics]
    B -->|LIKED<br />score: 5<br />liked_at: 2023-01-10| C

搭建Neo4j

启动Neo4j服务端

Neo4j的版本号比较混乱,可能看得人一头雾水,尤其是从2025年开始,Neo4j官方直接放弃了5.x系列版本号改成了按年月编号,不过目前生产环境Neo4j还是使用的5.x LTS版本,我们这里基于5.26版本演示后续内容。对于学习环境,我们可以直接使用Docker搭建。

docker run -d --name neo4j -p 7474:7474 -p 7687:7687 -e NEO4J_AUTH=none -e NEO4J_PLUGINS='["apoc"]' -e NEO4J_dbms_security_procedures_unrestricted=apoc.* -e NEO4J_apoc_export_file_enabled=true -e NEO4J_apoc_import_file_enabled=true -e NEO4J_apoc_import_file_use__neo4j__config=true neo4j:5.26

7474端口是Neo4j Browser端口,我们可以直接用浏览器访问。7687是Bolt协议端口,Bolt是Neo4j使用的二进制长连接协议,用于客户端和Neo4j服务端通信。NEO4J_AUTH环境变量用于设置Neo4j的认证用户名和密码,由于是学习环境我们这里直接关闭了认证,注意生产环境不要这么做。

其它参数都是APOC插件的,APOC(Awesome Procedures On Cypher)是一个非常流行的社区贡献库,它提供了许多实用的存储过程和函数扩展了Cypher的功能,包括数据导入导出等,生产环境一般都整合该插件使用。

访问Neo4j Browser

Neo4j启动后,我们可以使用浏览器访问Neo4j Browser,不必输入用户名密码,直接点击连接即可看到下面页面。

我们可以执行一个简单的Cypher查询来测试数据库是否正常工作。

RETURN "Hello, Neo4j!" AS greeting;

使用Cypher Shell

除了Neo4j Browser,Neo4j还提供了命令行工具Cypher Shell,它适合在服务器环境下执行脚本或批量操作。如果使用Docker部署,我们可以通过以下命令进入容器内的Cypher Shell。

docker exec -it neo4j cypher-shell

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。