在neo4j中使用Unwind和Dumping Data-查询优化

3
我正在使用批量插入的方式向neo4j数据库插入数据,但由于我的数据库不断增长,所以事务需要花费巨大的时间。
在我的项目中,有一种情况下,我有超过18,000条记录需要存储到数据库,并与目标节点建立关系。每条记录将被存储为友好节点
关系如下: Target_Node-[r:followed_by]->Friend_Node Target_Node-[r:Friends_with]->Friend_Node Target_Node-[r:Performs_Activity]->Friend_Node 我的查询将分别对所有情况执行,并且很可能一个目标和友好节点之间会有这三个关系之一。
对于单个插入,我为每个线程发送20条记录,对记录数组进行展开,检查记录是否已经存在于Friend_Node或Target_Node中,如果不存在,则将其创建为Friend_Node并将关系分配给它;如果节点已经具有关系并且传递给查询一个新关系,则还会在两个节点之间添加新关系。
此外,在我的查询中,我还会检查记录是否具有位置属性,如果是,则会创建一个位置节点并将其与之关联。 注意:create_rel变量可以是Friends_with、Followed_by或Activity_p。
我的查询如下:
 """UNWIND [{id: "1235" , uid : "0"}] as user

    UNWIND """+ l +""" as c

    OPTIONAL MATCH (n:Target {id : c.id , uid : "0"})

    OPTIONAL MATCH (m:Friend {id : c.id , screen_name:c.screen_name, uid : "0"})

    WITH coalesce(n, m) as node,user,c // returns first non-null value

    CALL apoc.do.when(node is null, "MERGE (n:Friend {id:c.id, name:c.name, profile: c.profile, location:c.location, uid : user.uid}) RETURN n", '', {c:c,user:user}) YIELD value

    with coalesce(node, value.n) as y,user,c

    MERGE (u:Target {id: user.id , uid : user.uid})

    """+create_rel+"""

    foreach (sc in c.cityn | merge(cn:Location {location:sc.location, loc_lower : sc.loc_lower}) merge (y)-[:`located_at`]-(cn))

    """

有时候,数据库会出现瞬态错误。

作为一名学习者,欢迎提供反馈并感谢有价值的建议。

提前致谢。

2个回答

0

我认为你的主要问题在于如何合并和匹配节点。理想情况下,您总是希望为节点拥有唯一标识符。我可以看到Friend节点具有一个属性id,我将假设每个FriendTargetid都是唯一的。

首先,您需要在该属性上创建唯一约束:

CREATE CONSTRAINT ON (f:Friend) ASSERT f.id IS UNIQUE;
CREATE CONSTRAINT ON (f:Target) ASSERT f.id IS UNIQUE;

你也想要类似于 Location 节点的东西。看起来你存储了位置值和位置的小写值,所以它们中的任何一个都应该对每个节点是唯一的。

CREATE CONSTRAINT ON (l:Location) ASSERT l.id IS UNIQUE;

现在你可以像这样优化你的查询:

"""UNWIND [{id: "1235" , uid : "0"}] as user

    UNWIND """+ l +""" as c

    OPTIONAL MATCH (n:Target {id : c.id})

    OPTIONAL MATCH (m:Friend {id : c.id})

    WITH coalesce(n, m) as node,user,c // returns first non-null value

    CALL apoc.do.when(node is null,
    "MERGE (n:Friend {id:c.id}) 
     ON CREATE SET n+= {name:c.name, profile: c.profile, 
           location:c.location, uid : user.uid} 
     RETURN n", '', {c:c,user:user})
    YIELD value
    with coalesce(node, value.n) as y,user,c
    MERGE (u:Target {id: user.id , uid : user.uid})
    """+create_rel+"""

    foreach (sc in c.cityn | 
        merge(cn:Location {location:sc.location}) 
        ON CREATE SET cn.loc_lower = sc.loc_lower 
        merge (y)-[:`located_at`]-(cn))

    """

我正在根据id创建索引,但在我的情况下无法使用唯一约束。原因是我的软件可能存在一个Friend节点,并且后来用户想要将其更改为目标,因此约束检查失败,因为在这种情况下我正在复制节点并仅更改用户id(如果需要的话)。还有其他建议吗? - Mubeen Mubarik
也许你应该重新考虑一下你的图形模式。 - Tomaž Bratanič

0
  1. 应避免同时运行多个写入查询(可能会触及相同的节点和关系),因为这可能会导致间歇性的TransientError,正如您所看到的。 (但是,导致瞬态错误的查询可以重试。)

  2. 应将userl作为parameters传递给查询,以便Cypher规划器只需要编译一次查询,并使查询不太容易受到Cypher注入攻击的影响。(此外,没有必要对始终只有一个映射的列表进行UNWIND - 您可以通过WITH {id:“1235”,uid:“0”} AS user 直接使用映射。但是,正如我提到的那样,您应该将user映射作为参数传递,以便在不强制重新编译的情况下高效更改用户。)

  3. 为避免重新编译,还需要将create_rel字符串设置为常量字符串(因此,最好直接放在主查询字符串中)。同样,您还应该将所需的任何变量作为参数传递。

  4. 应在:Target(id):Friend(id)上创建indexes(或唯一性约束),以加快MATCHMERGE子句的速度。

  5. (a)MERGE(u:Target {id:user.id,uid:user.uid})只需要执行一次,而不是每个c值执行一次。因此,应在UNWIND之前执行它。

    (b)此查询不严格需要创建u,因为查询中没有使用它。因此,您应该考虑将这个相同的MERGE子句拿出来,作为一个单独的独立查询运行,而不是每个线程运行一次。

这里有一个查询,结合了建议#2和#5a(但您必须自己处理其他内容),以及使用模式推导进行重构,以避免不必要的数据库访问:

MERGE (u:Target {id: $user.id, uid: $user.uid})
WITH u
UNWIND $l as c
WITH u, c, [(n:Target {id : c.id})-[*0]-()|n] AS nodeList
WITH u, c, CASE WHEN SIZE(nodeList) = 0 THEN [(n:Friend {id : c.id})-[*0]-()|n] ELSE nodeList END AS nodeList
CALL apoc.do.when(SIZE(nodeList) = 0, 'MERGE (n:Friend {id: c.id, name: c.name, profile: c.profile, location: c.location, uid: user.uid}) RETURN n', 'RETURN nodeList[0] AS n', {c:c,user:$user,nodeList:nodeList}) YIELD value
WITH u, c, value.n AS node
FOREACH (sc IN c.cityn | MERGE (cn:Location {location: sc.location, loc_lower: sc.loc_lower}) MERGE (node)-[:located_at]-(cn))

// Put your parameterized create_rel code here

谢谢您提供非常好的建议;它们确实很有帮助。因为我在这一点上有些困惑,您能否在查询中添加create_rel参数化代码,以便分配关系? - Mubeen Mubarik
期待您对此的建议。 - Mubeen Mubarik
我无法添加适当的参数化create_rel代码,因为您的问题从未显示原始的create_rel代码。我只是想说,您应该尝试不要将单独的create_rel字符串插入到此查询中 - 您应该直接尝试包含该字符串中的任何内容。如果不知道该字符串中有什么,我无法告诉您如何做。如果最终需要帮助创建一个单独的问题,那么您可能需要这样做。这个问题已经太复杂了。 - cybersam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接