Neo4j和Cypher:使用MERGE与模式索引/约束的关系说明

216 阅读4分钟

Neo4j和Cypher:使用MERGE与模式索引/约束的关系说明

几周前我写了关于cypher的MERGE函数,在过去的几天里,我一直在探索它是如何与模式索引唯一约束一起工作的。

成为一名开发者的激动人心的时刻

关于Neo4j和Cypher的合并,现在可以说的太多了,但肯定有理由指出,这次合并可能会在编程领域带来许多令人兴奋的发展。

当程序员得到他们所需要的产品和工具来完成他们的工作时,他们几乎总是很感激,而现在正是采取这样的措施的时候了。Neo4J和Cypher决定合并的事实意味着两者的优势将很快显现出来。

你应该使用所有最好的工具来为你的下一个软件项目做出明智的决定,而实现这一目标的一个好方法就是使用已经给你的关于产品功能的东西。这就是说,你可以同时利用Neo4J和Cypher的优点,拿出你需要的确切工具,在你的影响范围内有所作为。

其他产品会很快合并吗?

其他软件开发产品也有一些考虑合并的强烈需求。编码员和程序员希望以他们最喜欢的项目的确切方式使用它们,这意味着让它们以对程序员有用的方式进行合并。他们只是希望能够尽可能多地从每个程序中挤出更多的用途。

你要确保你能看到你的代码是怎么回事,因为你直接将它们应用于你此时正在处理的任何问题。可以肯定的是,这不是一项容易的任务,但从来没有人说它会很容易。重要的是,你要把工作完成,这样你就可以开始在你现在所做的编码中变得更有成效。

Neo4j的一个常见用例是对用户和事件进行建模,其中一个事件可能是一条推特、Facebook帖子或Pinterest图钉。这个模型可能看起来像这样。

我们会有一个(用户,事件)对的数据流和一个类似下面的加密语句来把数据输入Neo4j。

我们想确保我们不会得到重复的用户或事件,而MERGE提供了这样的语义。

MERGE (u:User {id: {userId}})
MERGE (e:Event {id: {eventId}})
MERGE (u)-[:CREATED_EVENT]->(m)
RETURN u, e

我们希望确保我们不会得到重复的用户或事件,MERGE提供了这样的语义:

MERGE确保图中存在一个模式。该模式要么已经存在,要么需要创建。

import org.neo4j.cypher.javacompat.ExecutionEngine;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;
import org.neo4j.helpers.collection.MapUtil;
import org.neo4j.kernel.impl.util.FileUtils;
 
...
 
public class MergeTime
{
    public static void main(String[] args) throws Exception
    {
        String pathToDb = "/tmp/foo";
        FileUtils.deleteRecursively(new File(pathToDb));
 
        GraphDatabaseService db = new GraphDatabaseFactory().newEmbeddedDatabase( pathToDb );
        final ExecutionEngine engine = new ExecutionEngine( db );
 
        ExecutorService executor = Executors.newFixedThreadPool( 50 );
        final Random random = new Random();
 
        final int numberOfUsers = 10;
        final int numberOfEvents = 50;
        int iterations = 100;
        final List<Integer> userIds = generateIds( numberOfUsers );
        final List<Integer> eventIds = generateIds( numberOfEvents );
        List<Future> merges = new ArrayList<>(  );
        for ( int i = 0; i < iterations; i++ )
        {
            Integer userId = userIds.get(random.nextInt(numberOfUsers));
            Integer eventId = eventIds.get(random.nextInt(numberOfEvents));
            merges.add(executor.submit(mergeAway( engine, userId, eventId) ));
        }
 
        for ( Future merge : merges )
        {
            merge.get();
        }
 
        executor.shutdown();
 
        ExecutionResult userResult = engine.execute("MATCH (u:User) RETURN u.id as userId, COUNT(u) AS count ORDER BY userId");
 
        System.out.println(userResult.dumpToString());
 
    }
 
    private static Runnable mergeAway(final ExecutionEngine engine,
                                      final Integer userId, final Integer eventId)
    {
        return new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    ExecutionResult result = engine.execute(
                            "MERGE (u:User {id: {userId}})\n" +
                            "MERGE (e:Event {id: {eventId}})\n" +
                            "MERGE (u)-[:CREATED_EVENT]->(m)\n" +
                            "RETURN u, e",
                            MapUtil.map( "userId", userId, "eventId", eventId) );
 
                    // throw away
                    for ( Map<String, Object> row : result ) { }
                }
                catch ( Exception e )
                {
                    e.printStackTrace();
                }
            }
        };
    }
 
    private static List<Integer> generateIds( int amount )
    {
        List<Integer> ids = new ArrayList<>();
        for ( int i = 1; i <= amount; i++ )
        {
            ids.add( i );
        }
        return ids;
    }
}

我们最多创建10个用户和50个事件,然后用50个并发线程对随机(用户,事件)做100次迭代。之后,我们执行一个查询,检查每个id有多少用户被创建,得到如下输出。

+----------------+
| userId | count |
+----------------+
| 1      | 6     |
| 2      | 3     |
| 3      | 4     |
| 4      | 8     |
| 5      | 9     |
| 6      | 7     |
| 7      | 5     |
| 8      | 3     |
| 9      | 3     |
| 10     | 2     |
+----------------+
10 rows

接下来,我在用户和事件上添加了一个模式索引,看看这是否会有什么不同,这是Javad Karabi最近在用户组上提出的问题

CREATE INDEX ON :User(id)
CREATE INDEX ON :Event(id)

我们不会期望这有什么不同,因为模式索引并不确保唯一性,但我还是运行了它,得到了以下输出:

+----------------+
| userId | count |
+----------------+
| 1      | 2     |
| 2      | 9     |
| 3      | 7     |
| 4      | 2     |
| 5      | 3     |
| 6      | 7     |
| 7      | 7     |
| 8      | 6     |
| 9      | 5     |
| 10     | 3     |
+----------------+
10 rows

如果我们想确保用户和事件的唯一性,我们需要在这两个标签的id上添加一个唯一约束:

CREATE CONSTRAINT ON (user:User) ASSERT user.id IS UNIQUE
CREATE CONSTRAINT ON (event:Event) ASSERT event.id IS UNIQUE

现在如果我们运行这个测试,我们最终只会得到每个用户的一个:

+----------------+
| userId | count |
+----------------+
| 1      | 1     |
| 2      | 1     |
| 3      | 1     |
| 4      | 1     |
| 5      | 1     |
| 6      | 1     |
| 7      | 1     |
| 8      | 1     |
| 9      | 1     |
| 10     | 1     |
+----------------+
10 rows

如果我们运行一个类似的查询来检查事件的唯一性,我们会看到同样的结果。

就我所知,我们合并的这种重复的节点只发生在你试图同时创建同一个节点两次的时候。一旦节点被创建,我们可以使用MERGE与非唯一索引,重复的节点就不会被创建。

如果你想玩一玩,这篇文章的所有代码都可以在gist中找到。