Neo4j 图数据库操作与数据导入实战
Neo4j 图数据库操作与数据导入实战
一份涵盖 Cypher 查询、CSV 批量导入、Python 动态建边、索引优化与 Docker 部署的 Neo4j 实践笔记。
前言
在知识图谱、股权穿透、关系推理等场景中,Neo4j 是常用的图数据库。本文基于实际项目经验,梳理了从数据建模、节点/关系导入、查询、删除,到索引创建和 Docker 全量同步的关键操作。无论你是初次接触图数据库,还是希望提升导入效率,都能在这里找到可直接复用的参考。
数据模型示例(以公司-股东为例):
- 节点标签:
Company(公司)、Shareholders/People(股东/自然人) - 关系类型:
法人股东、自然人股东,并携带属性percent(持股比例)、open_flag(是否公开)
一、基础查询:匹配公司及其股东
MATCH (c:Company {name: '北京万合漫旅信息技术有限公司'})-[r:法人股东|自然人股东]-(p)
RETURN c, COLLECT(DISTINCT p) AS shareholders
- 通过
|匹配多种关系类型,一次找出所有股东。 COLLECT(DISTINCT p)去重后返回股东列表,避免重复。- 该查询可用于股权穿透的基础信息获取。
二、数据导入
2.1 使用 LOAD CSV 导入节点
LOAD CSV WITH HEADERS FROM 'file:///data/vc_nodes.csv' AS row
CREATE (:Company {id: row.`:ID(VC-ID)`, name: row.name})
- 文件需放在 Neo4j 的
import目录下。 id: row.:ID(VC-ID)`` 将 CSV 中的 ID 列直接作为节点的id属性,便于后续关系匹配。
2.2 LOAD CSV 导入关系(固定关系类型)
LOAD CSV WITH HEADERS FROM 'file:///data/gd_rels.csv' AS row
MATCH (start:Shareholders {id: row.`:START_ID(Shareholders-ID)`})
MATCH (end:Company {id: row.`:END_ID(Shareholders-ID)`})
CREATE (start)-[:自然人股东 {percent: toFloat(row.`percent:float`), open_flag: toInteger(row.`open_flag:int`)}]->(end)
- 这里的关系类型被硬编码为
自然人股东,无法根据 CSV 内容动态变化。 - 若 CSV 中包含多种关系类型(如法人股东、自然人股东),上述写法只能导入其中一种。
2.3 Python 动态导入多种关系
为了解决 LOAD CSV 的动态关系限制,使用 Python 驱动循环读取 CSV 并创建关系:
from neo4j import GraphDatabase
import csv
g = GraphDatabase.driver(GRAPH_DB['uri'], auth=(GRAPH_DB['user'], GRAPH_DB['password']))
def create_relationship(start_node, end_node, start_id, end_id, rel_type, percent, open_flag):
query = (
"MATCH (p:%s),(q:%s) "
"WHERE p.id='%s' AND q.id='%s' "
"CREATE (p)-[rel:%s{percent:'%s', open_flag:'%s'}]->(q)"
) % (start_node, end_node, start_id, end_id, rel_type, percent, open_flag)
with g.session(database='neo4j') as session:
session.run(query)
with open('/Users/yqy/neo4j/data/gd_rels.csv', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
start_id = int(row[':START_ID(Shareholders-ID)'])
end_id = int(row[':END_ID(Shareholders-ID)'])
percent = float(row['percent:float'])
open_flag = int(row['open_flag:int'])
relation_type = row[':TYPE'] # 动态读取关系类型
create_relationship('People', 'Company', start_id, end_id,
relation_type, percent, open_flag)
- 此方法可以根据 CSV 的
:TYPE字段动态创建法人股东、自然人股东等不同关系。 - 务必保证节点 ID 唯一,且事先导入所有节点,否则创建关系时会一直执行但无实际效果(见下文注意点)。
三、删除操作
// 删除特定关系
MATCH (:Shareholders)-[r:`股东`]->(:Company)
DELETE r
// 分批删除节点(防内存溢出)
MATCH (n:Shareholders)
WITH n LIMIT 1000000
DELETE n
RETURN count(*)
- 大批量删除节点时建议分批 + LIMIT,避免一次性加载过多节点导致 OOM。
- 删除关系必须在删除节点前执行,否则会因外键约束报错。
四、关键注意点
4.1 ID 匹配问题
- 执行
LOAD CSV或 Python 建关系时,如果 START_ID 或 END_ID 对应的节点不存在,Cypher 会返回no changes, no records,而 Python 脚本并不会报错,只会静默跳过。 - 务必检查节点 ID 完全对应,否则会在无声中丢失大量关系。
4.2 关系设计建议
统一关系方向与语义:A 与 B 的具体关系由关系的属性来区分,而非创建大量不同的关系类型。
例如,不要创建大股东、小股东等多种类型,而是统一用股东关系,内部用type或share_percent属性标记类别。
(但若业务上需要快速检索某一类关系,保留少量关系类型也是合理选择。)
五、索引优化
CREATE INDEX ON :Shareholders(name)
- 对高频查询字段(如
name、id)建立索引,能显著加速MATCH和WHERE。 - 企业版 Neo4j 支持更多索引类型(如全文索引、复合索引),可按需查阅文档。
六、Docker 全量离线导入(高性能)
对于大规模数据,可使用 neo4j-admin import 工具直接生成数据库文件,导入速度远超逐条 INSERT。
docker run -d --name neo4ji \
-p 7474:7474 -p 7687:7687 \
-v /tidb/neo4j/data:/data \
-v /tidb/neo4j/logs:/logs \
-v /tidb/neo4j/import:/var/lib/neo4j/import \
--env-file ./env.list \
neo4j:enterprise \
bin/neo4j-admin import \
--nodes=import/gd_nodes_v1.csv \
--relationships=import/gd_rels.csv \
--multiline-fields=True \
--skip-bad-relationships=True \
--database=register
参数解读:
--nodes:指定节点 CSV,可多次出现。--relationships:指定关系 CSV,格式必须为:START_ID,:END_ID,:TYPE及属性列。--multiline-fields=True:允许 CSV 字段中包含换行符。--skip-bad-relationships=True:跳过引用不存在节点的关系,并记录日志。--database=qmpregister:生成名为qmpregister的数据库,而非默认neo4j。
注意事项:
- 使用
neo4j:enterprise镜像(社区版不支持部分高级功能)。 - 容器启动时会执行导入并创建数据库,挂载路径需提前准备好 CSV 文件。
- 更多细节参考官方文档:https://neo4j.com/docs/operations-manual/current/docker/introduction/
结语
本文总结了 Neo4j 从查询到大规模导入的常见操作,尤其突出了 动态关系导入的 Python 方案 与 Docker 离线导入的高效实践。图数据库的精髓在于关系,正确处理好 ID 匹配和关系建模,能让后续查询又快又准。建议将上述命令封装为脚本,结合 CI/CD 定期更新,以维持图谱数据的鲜活。
如果你在操作中遇到特殊问题,欢迎在评论区交流,希望这份速查手册能帮你少走弯路。

浙公网安备 33010602011771号