将一批用户关系导入到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 driverNeo4j官方驱动能够借助.来开展执行操作, 并且运用参数进行传值。密码切勿写死于代码当中, 此类事物早晚将会被提交至仓库。开始导数据前我会先把唯一约束建好。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}), uiduser[uid], nameuser[name], )问题有两个一条数据一次网络交互而且脚本不能安全重跑。我通常把数据切成小批次再用 展开。defimport_users(driver, users, chunk_size800): 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, rowsrows, 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 , linksrelations, database_neo4j, )这里, MERGE所承担的职责仅仅是判定关系有无存在的情况, 而这种说不定会出现改变的字段, 则是放置于SET当中句号。要是写成下面这样MERGE (source)-[:FOLLOWS {channel: link.channel}]-(target)用户更改一回关注途径或许就会多生出一条关系, 语法并无差错, 然而模型有误, 并且这类问题一般要等查询至结果循环之时才为人察见。查询图关系时也别一兴奋就写无限深度路径。deffind_following(driver, uid, max_rows50): 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 , uiduid, limitmax_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_iddocument_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_iddocument_id, tagstags, ).consume with driver.session(databaseneo4j) 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 循环看见了就尽早删。