3、Neo4j入门:安装配置、Cypher基础、节点与关系建模

这一章我们聊 Neo4j。说实话,我第一次接触图数据库就是它。当时项目里药物关联查询用关系型数据库写了好几层 JOIN,性能差得离谱。换成 Neo4j 后,同样的查询从秒级压到毫秒级。嗯,今天咱们就从零开始,把它装好,写几条 Cypher,再琢磨怎么把药物、疾病、患者这些实体抽象成图。

3.1 安装与配置

Neo4j 社区版完全够用。去官网下载,解压就能跑。我个人习惯用 tar 包或者 zip 包,干净利落。

# 解压
tar -xzf neo4j-community-5.x.x-unix.tar.gz
cd neo4j-community-5.x.x

# 启动
./bin/neo4j start

# 查看状态
./bin/neo4j status

启动后打开浏览器访问 http://localhost:7474。默认用户名密码都是 neo4j,第一次会让你改密码。我建议改成自己常用的,但别太简单,毕竟数据库里可能存着真实病历。

端口提醒: 7474 是 HTTP 端口,7687 是 Bolt 协议端口。如果本地端口被占,可以在 conf/neo4j.conf 里改 server.http.listen_address 和 server.bolt.listen_address。

Windows 用户更简单,下载 exe 安装包,一路 Next。装完后在系统服务里能看到 Neo4j,可以直接启动。

别踩这个坑: 我遇到过几次内存不够导致启动失败。Neo4j 默认堆内存 1G,如果你的机器只有 2G,建议调小。conf/neo4j.conf 里找 dbms.memory.heap.initial_size 和 dbms.memory.heap.max_size。

3.2 Cypher 基础

Cypher 是 Neo4j 的查询语言,长得像 SQL 但专门为图设计。说白了就是用 ASCII 画图。(n) 表示节点,-[r]-> 表示关系。

CREATE & MATCH

// 创建一个药物节点
CREATE (d:Drug {name: "阿司匹林", dosage: "100mg"})

// 创建一个疾病节点
CREATE (dis:Disease {name: "感冒"})

// 创建关系
MATCH (d:Drug {name: "阿司匹林"}), (dis:Disease {name: "感冒"})
CREATE (d)-[:TREATS {effectiveness: "中"}]->(dis)

注意,MATCH 先找到节点,CREATE 再连关系。如果你直接 CREATE (d)-[...]->(dis) 而不先 MATCH,Neo4j 会创建全新的节点和关系——数据库里就会多出一堆重复数据。我曾经吃过这个亏,导入 10 万条数据后发现多了一倍节点,血泪教训。

查询与过滤

// 查询所有药物
MATCH (d:Drug) RETURN d.name, d.dosage

// 带条件查询
MATCH (d:Drug)-[:TREATS]->(dis:Disease)
WHERE dis.name = "高血压"
RETURN d.name, d.dosage

WHERE 子句支持 AND、OR、IN、CONTAINS 等。你想查找名称包含“阿司”的药物,可以写 WHERE d.name CONTAINS "阿司"。

删除与更新

// 删除节点(先删关系)
MATCH (d:Drug {name: "阿司匹林"})-[r]-()
DELETE r, d

// 更新属性
MATCH (d:Drug {name: "阿司匹林"})
SET d.dosage = "500mg"

每次删除节点前,必须先把它的关系删干净,否则报错。我习惯先 MATCH (n)-[r]-() DELETE r, n,一步到位。

3.3 节点与关系建模

图模型的核心就三个东西:节点、关系、属性。节点可以有标签(Label),关系必须有类型(Type),属性是键值对。你想想看,一个药物节点:(:Drug {name, dosage, manufacturer}),一个患者节点:(:Patient {name, age, gender}),它们之间用 -[:PRESCRIBED {date, dose}]-> 连接。

概念Cypher 表示说明
节点(n:Label {prop: val})标签可多个,n 是变量名
关系-[r:TYPE {prop: val}]->必须有方向,也可以双向
属性支持字符串、数值、布尔、列表等用 . 访问

建模原则

  • 节点代表实体:药物、疾病、患者、医生、药房。不要为了省事把多个实体塞到一个节点里。
  • 关系代表动作或关联:PRESCRIBED、SUFFERS_FROM、MANUFACTURED_BY。动词命名。
  • 属性放对地方:节点的固有属性(颜色、重量)放节点上,关系的动态属性(数量、日期)放关系上。举个例子,患者就诊日期应该放在 VISITED 关系上,而不是患者节点。
  • 方向别乱:虽然 Cypher 可以忽略方向查询,但建模时最好从高频查询角度决定方向。如果经常查“某药能治哪些病”,把方向从药指向病。

一个简单的药物知识图谱模型

下面这张图展示了五个节点和四条关系。每个节点带一个标签和两个示例属性,关系也有属性。你一眼就能看出图数据库是怎么把数据串起来的。

药物知识图谱图模型示例 :Patient name: "张三" age: 45 :Doctor name: "李医生" specialty: "内科" :Drug name: "降压药A" dosage: "10mg" :Disease name: "高血压" icd: "I10" :SideEffect name: "头痛" severity: "轻度" VISITED date: "2025-03-10" PRESCRIBED dose: "10mg" TREATS HAS_SIDE_EFFECT incidence: "15%"

对应的 Cypher 创建语句也很直观:

CREATE (p:Patient {name: "张三", age: 45})
CREATE (doc:Doctor {name: "李医生", specialty: "内科"})
CREATE (d:Drug {name: "降压药A", dosage: "10mg"})
CREATE (dis:Disease {name: "高血压", icd: "I10"})
CREATE (se:SideEffect {name: "头痛", severity: "轻度"})

CREATE (p)-[:VISITED {date: "2025-03-10"}]->(doc)
CREATE (doc)-[:PRESCRIBED {dose: "10mg"}]->(d)
CREATE (d)-[:TREATS]->(dis)
CREATE (d)-[:HAS_SIDE_EFFECT {incidence: "15%"}]->(se)

3.4 建模实践小建议

别一上来就照搬关系型数据库的表结构。我见过团队把药物属性拆成几十个节点,每个属性一个标签,结果一个查询要跳七八步。图数据库的优势是关联深度,不是属性归一化。合理的做法是:把经常一起查的属性放在一个节点里,用关系表达关联而不是用外键。

一个快速检查方法: 写出你最常用的 5 个查询,看看它们需要遍历多少步。如果大部分查询只需要 1~2 步关系,模型就合理。如果需要 5 步以上,考虑合并节点或者改方向。

最后提一下批量导入。如果已经有几万条药物数据,别用 CREATE 一句句跑。用 LOAD CSV 或者 Neo4j Admin Import 工具。我上次迁移 50 万条处方数据,用 LOAD CSV 配合 USING PERIODIC COMMIT,十分钟搞定。

USING PERIODIC COMMIT 500
LOAD CSV WITH HEADERS FROM "file:///drugs.csv" AS row
CREATE (d:Drug {
    name: row.name,
    category: row.category,
    dosage: row.dosage
})
注意文件路径: CSV 文件要放在 Neo4j 的 import 目录下,否则读不到。我最初不知道,折腾了半小时。

好了,安装、基础 Cypher、建模思路都过了一遍。把你手里的药物数据画成图模型,写几条 Cypher 试试,很快就能上手。