Python 操作 Neo4j,别一上来就用 for 循环写节点
2026/9/11 19:25:07 网站建设 项目流程

将一批用户关系导入到Neo4j中, 脚本运行时没有出现报错情况, 日志所显示的内容也全都表明是成功的, 然而数据库里的节点数量却在运行过程中越来越多。

像这种问题, 我通常情况下, 不会首先去怀疑Neo4j, 而是会先去查看写入代码。果真如此, 外层存在着一个for循环, 在其里面, 每一次执行的时候呢只是执行一条。脚本重新运行一次, 数据紧跟着就会被复制一遍。

图数据库写起来的时候, 看着是比较直观的。然而, 真正容易出现问题的那些地方, 却是唯一约束, 还有批量写入, 以及事务边界。

连接 Neo4j,用官方驱动就够了:

pip install neo4j

不能够让连接代码, 零碎地分布于业务函数之中, 而且, 绝对不可以每处理一条数据, 便去创建一次。

import os from neo4j import GraphDatabase defbuild_driver: uri = os.getenv("NEO4J_URI", "neo4j://127.0.0.1:7687") user = os.getenv("NEO4J_USER", "neo4j") password = os.environ["NEO4J_PASSWORD"] driver = GraphDatabase.driver(uri, auth=(user, password)) driver.verify_connectivity return driver

Neo4j官方驱动能够借助.来开展执行操作, 并且运用参数进行传值。密码切勿写死于代码当中, 此类事物早晚将会被提交至仓库。

开始导数据前,我会先把唯一约束建好。

definit_schema(driver): driver.execute_query( """ CREATE CONSTRAINT user_uid_unique IF NOT EXISTS FOR (u:User) REQUIRE u.uid IS UNIQUE """, database_="neo4j", )

要是没有这样的约束, 下面有着 MERGE 运用的这条语句, 一旦数据模型给写得杂乱无章, 后续去排列重复节点, 那依旧是会麻烦的。

仅是唯一约束, 并非仅仅是对脏数据加以拦截, 其背后并且会构建起与之对应的索引, 当已然存在重复数据之际, 约束的创建极有可能会直接遭遇失败, 就在这个时段, 切莫反复去执行脚本, 先要把重复的节点查找出来并予以处理。

真正写用户节点时,别这么干:

for user in users: driver.execute_query( "CREATE (:User {uid: $uid, name: $name})", uid=user["uid"], name=user["name"], )

问题有两个:一条数据一次网络交互,而且脚本不能安全重跑。

我通常把数据切成小批次,再用 展开。

defimport_users(driver, users, chunk_size=800): cypher = """ UNWIND $rows AS row MERGE (u:User {uid: row.uid}) SET u.name = row.name, u.department = row.department, u.updated_at = datetime """ for offset in range(0, len(users), chunk_size): rows = users[offset: offset + chunk_size] driver.execute_query( cypher, rows=rows, database_="neo4j", )

会将参数里的列表呈现为展开成为多行的形式, 后续的 MERGE 以及 SET 便能够依照行来进行处理, 相较于在外面逐个逐条地进行提交, 要干净许多得多。

写入关系也还算可以, 然而这儿存在一处地方, 不少人我都见过在这个地方写错: 把能够发生变化的属性塞进到MERGE当中。

defimport_follow_relations(driver, relations): driver.execute_query( """ UNWIND $links AS link MATCH (source:User {uid: link.source_uid}) MATCH (target:User {uid: link.target_uid}) MERGE (source)-[r:FOLLOWS]->(target) ON CREATE SET r.created_at = datetime SET r.channel = link.channel, r.updated_at = datetime """, links=relations, database_="neo4j", )

这里, MERGE所承担的职责仅仅是判定关系有无存在的情况, 而这种说不定会出现改变的字段, 则是放置于SET当中句号。

要是写成下面这样:

MERGE (source)-[:FOLLOWS {channel: link.channel}]->(target)

用户更改一回关注途径或许就会多生出一条关系, 语法并无差错, 然而模型有误, 并且这类问题一般要等查询至结果循环之时才为人察见。

查询图关系时,也别一兴奋就写无限深度路径。

deffind_following(driver, uid, max_rows=50): records, _, _ = driver.execute_query( """ MATCH path = (:User {uid: $uid})-[:FOLLOWS*1..2]->(target:User) RETURN DISTINCT target.uid AS uid, target.name AS name, length(path) AS distance ORDER BY distance, uid LIMIT $limit """, uid=uid, limit=max_rows, database_="neo4j", ) return [record.data() for record in records]

1..2 这个范围, 是不可以随手就删除的。在关系密集的图当中, 当路径深度放开以后, 中间结果是有可能迅速膨胀起来的。倘若页面仅仅展现几十条数据, 那就不要让数据库先把整张关系网都翻上一遍。

还有一种操作,必须放在事务里:先删除旧关系,再重建新关系。

defreplace_document_tags(driver, document_id, tags): defwrite_tags(tx): tx.run( """ MATCH (:Document {id: $document_id})-[r:HAS_TAG]-> DELETE r """, document_id=document_id, ).consume tx.run( """ MATCH (doc:Document {id: $document_id}) UNWIND $tags AS tag_name MERGE (tag:Tag {name: tag_name}) MERGE (doc)-[:HAS_TAG]->(tag) """, document_id=document_id, tags=tags, ).consume with driver.session(database="neo4j") as session: session.execute_write(write_tags)

成功删除了过后又出现重建失败的情况, 文档标签就变为空的状态了。将两个动作放入到同一个事务之中, 要么一起进行提交, 要么一起进行回滚。它本身会自动运用事务;当涉及到多条查询以及中间处理的时候, 再去使用这种托管事务。

最后别忘了关连接:

defrun_import(users, relations): driver = build_driver try: init_schema(driver) import_users(driver, users) import_follow_relations(driver, relations) finally: driver.close

操作 Neo4j 的代码不算多,坑主要藏在 里。

是否存在稳定业务主键于节点, MERGE所匹配的是哪些字段, 关系可不可以重复,路径最大能查询到几层, 批量任务失败之后可不可以重新运行——在这些地方不预先考虑明白清晰清楚, 即便驱动连接再怎么美观漂亮好看也毫无作用用处价值。

尤其是那个套着 的 for 循环,看见了就尽早删。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询