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,第一次会让你改密码。我建议改成自己常用的,但别太简单,毕竟数据库里可能存着真实病历。
conf/neo4j.conf 里改 server.http.listen_address 和 server.bolt.listen_address。
Windows 用户更简单,下载 exe 安装包,一路 Next。装完后在系统服务里能看到 Neo4j,可以直接启动。
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 可以忽略方向查询,但建模时最好从高频查询角度决定方向。如果经常查“某药能治哪些病”,把方向从药指向病。
一个简单的药物知识图谱模型
下面这张图展示了五个节点和四条关系。每个节点带一个标签和两个示例属性,关系也有属性。你一眼就能看出图数据库是怎么把数据串起来的。
对应的 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 建模实践小建议
别一上来就照搬关系型数据库的表结构。我见过团队把药物属性拆成几十个节点,每个属性一个标签,结果一个查询要跳七八步。图数据库的优势是关联深度,不是属性归一化。合理的做法是:把经常一起查的属性放在一个节点里,用关系表达关联而不是用外键。
最后提一下批量导入。如果已经有几万条药物数据,别用 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
})
import 目录下,否则读不到。我最初不知道,折腾了半小时。
好了,安装、基础 Cypher、建模思路都过了一遍。把你手里的药物数据画成图模型,写几条 Cypher 试试,很快就能上手。