CRUDRepository的保存方法很慢?

6
我希望在我的neo4j数据库中存储一些数据。我使用spring-data-neo4j来实现这个目的。
我的代码如下:
    for (int i = 0; i < newRisks.size(); i++) {
        myRepository.save(newRisks.get(i));
        System.out.println("saved " + newRisks.get(i).name);
    }

我的newRisks数组包含大约60000个对象和60000条边。每个节点和边都有一个属性。
这个循环的持续时间约为15-20分钟,这正常吗? 我使用Java VisualVM搜索一些瓶颈,但我的平均CPU使用率为10-25%(4个核心),我的堆不到一半。
有什么选项可以提高此操作的速度吗?
编辑:另外,在第一次调用myRepository.save(newRisks.get(i))时,jvm会在几分钟后睡眠,然后才会输出第一个输出。
第二次编辑:
Risk类:
@NodeEntity
public class Risk {
    //...
    @Indexed
    public String name;

    @RelatedTo(type = "CHILD", direction = Direction.OUTGOING)
    Set<Risk> risk = new HashSet<Risk>();

    public void addChild(Risk child) {
        risk.add(child);
    }

    //...
}

创建风险:
@Autowired
private Repository myRepository;

@Transactional
public Collection<Risk> makeSomeRisks() {

    ArrayList<Risk> newRisks = new ArrayList<Risk>();

    newRisks.add(new Risk("Root"));

    for (int i = 0; i < 60000; i++) {
        Risk risk = new Risk("risk " + (i + 1));
        newRisks.get(0).addChild(risk);
        newRisks.add(risk);
    }

    for (int i = 0; i < newRisks.size(); i++) {
        myRepository.save(newRisks.get(i));
    }

    return newRisks;
}

根据文档,CRUDRepository.save可以接受Iterable作为参数。为什么不直接使用myRepository.save(newRisks)呢? - Thomas
我试过了,它也可以工作。但是,它不够快。所以我可以看出,它并没有死掉 ^^ - hilbert
你能展示一下你的类的结构以及它所涉及到的任何节点实体和关系实体吗?还有你的风险实例的构建方法是什么? - Michael Hunger
还有你的事务边界是什么?它应该在你的方法周围使用 @Transactional,或者可能是一个 Transaction-Template,在每10k个对象周围提交。否则这将为每个对象创建一个 tx,这是非常繁琐的。 - Michael Hunger
@Michael Hunger,感谢您提出的额外问题,我已在我的帖子中添加了信息,位于“第二次编辑:”下面。 - hilbert
4个回答

5
我认为我找到了一个解决方案:
我尝试使用原生的Neo4j Java API 进行相同的插入操作:
GraphDatabaseService graphDb;
Node firstNode;
Node secondNode;
Relationship relationship;

graphDb = new EmbeddedGraphDatabase(DB_PATH);
Transaction tx = graphDb.beginTx();

try {
    firstNode = graphDb.createNode();
    firstNode.setProperty( "name", "Root" );

    for (int i = 0; i < 60000; i++) {
        secondNode = graphDb.createNode();
        secondNode.setProperty( "name", "risk " + (i+1));

        relationship = firstNode.createRelationshipTo( secondNode, RelTypes.CHILD );
    }
    tx.success();
}
finally {
    tx.finish();
    graphDb.shutdown();
}

结果:

几秒后,数据库被风险填满。

也许是这些反射通过spring-data-neo4j减慢了此例程。@Michael Hunger在他的书GoodRelationships中说了类似的话,感谢这个提示。


5
这里的问题在于您正在使用不适合大规模插入的API。您创建了一个风险以及60k个子节点,首先保存根节点,同时也持久化了60k个子节点(并创建关系)。这就是为什么第一次保存时间如此之长的原因,然后您又再次保存了这些子节点。
这里有一些可以加速SDN的解决方案:
  1. 不要使用集合方法进行大量插入,持久化两个参与者并使用template.createRelationshipBetween(root,child,“CHILD”,false);
  2. 先保存子节点,然后将所有已持久化的子节点添加到根对象中并保存它
  3. 像您所做的那样,使用Neo4j-Core API,但调用template.postEntityCreation(node,Risk.class),以便可以通过SDN访问实体。然后您还必须自己对实体进行索引(db.index.forNodes(“Risk”)。add(node,“name”,name);)(或使用neo4j核心API自动索引,但与SDN不兼容)。
  4. 无论是使用core-api还是SDN,最佳性能下应该将tx-sizes设置为约10-20k nodes/rels。

谢谢,你说得对。首先我只保存了根目录,只花了6分钟。也许以后我会尝试你提出的其他解决方案。 - hilbert

1
我遇到了和OP一样的问题。在我的情况下,将Neo4j的使用方式从远程服务器模式改为嵌入式模式非常有用。关于嵌入式SDN的使用,可以在这里找到很好的例子。

1

在数据库中进行插入操作(不通过Java),是否存在同样的延迟问题,还是这只是Spring Data特有的问题?


好主意,但我怎么能在不使用其他编程语言的情况下插入这些笔记的数量等信息呢?另一个想法是:我可以尝试使用标准的Neo4j Java API,而不是Spring-Data-Neo4j。 - hilbert
在您选择的 SQL 编辑器中执行插入语句。这样可以测试数据库本身的速度,不受任何编程语言的影响。 - abehrens
这是一个NoSQL数据库,但我已经尝试过使用Neo4j的本地Java API,速度更快。 - hilbert

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