我在我的应用程序中发现了一个并发问题,其中有两个线程尝试在 Neo4J v2.2.5 中的同一节点或关系上同时执行写入操作。
我设法用简单的方法重现了这个问题:
下载并导入示例 Neo4j 电影数据库:http://example-data.neo4j.org/files/cineasts_12k_movies_50k_actors_2.1.6.zip 由于数据库较旧,因此需要在
conf/neo4j.properties
中添加allow_store_upgrade=true
来启用数据库自动升级。启动 neo4j 并在 neo4jshell 上运行此查询:
match (a:Actor {name: "Claude Jade"}), (m:Movie) merge (a)-[:ACTS_IN]->(m);
这将创建从“Claude Jade” Actor 到所有电影节点的 ACTS_IN 关系。这样做的原因是为了让删除“Claude Jade”Actor节点的过程(见下面的第4点)更长,并发问题发生的机会更大。
如果您还没有curl 命令行程序,请下载它。我们将使用curl向neo4j发送查询。
创建一个 bash 脚本文件(文件名由您决定),内容为:
#!/bin/bash curl -XPOST http://localhost:7474/db/data/transaction/commit -H "Content-Type: application/json" -d '{"statements" : [ {"statement" : "MATCH (n:Actor {name: \"Claude Jade\"}) OPTIONAL MATCH (n)-[r]-() DELETE n, r"} ]}' & curl -XPOST http://localhost:7474/db/data/transaction/commit -H "Content-Type: application/json" -d '{"statements" : [ {"statement" : "MATCH (n:Actor {name: \"Claude Jade\"}) CREATE (n)-[:ACTS_IN]->(m:Movie {title: \"Hello World\"}) RETURN m"} ]}' & wait
这将并行运行两个curl进程,其中第一个进程将尝试删除“Claude Jade”Actor及其所有关系,第二个进程将尝试创建与“Claude Jade”Actor的新ACTS_IN关系。
在 bash 上运行脚本文件,例如
$ ./test.sh
这是我得到的结果:
{
"results" : [{
"columns" : ["m"],
"data" : [{
"row" : [{
"title" : "Hello World"
}
]
}
]
}
],
"errors" : []
} {
"results" : [{
"columns" : [],
"data" : []
}
],
"errors" : [{
"code" : "Neo.DatabaseError.Transaction.CouldNotCommit",
"message" : "org.neo4j.kernel.api.exceptions.TransactionFailureException: Node record Node[3150,used=false,group=55,prop=-1,labels=Inline(0x0:[]),light] still has relationships",
"stackTrace" : "java.lang.RuntimeException: org.neo4j.kernel.api.exceptions.TransactionFailureException: Node record Node[3150,used=false,group=55,prop=-1,labels=Inline(0x0:[]),light] still has relationships\r\n\tat org.neo4j.server.rest.transactional.TransitionalTxManagementKernelTransaction.commit(TransitionalTxManagementKernelTransaction.java:87)\r\n\tat org.neo4j.server.rest.transactional.TransactionHandle.closeContextAndCollectErrors(TransactionHandle.java:278)\r\n\tat
...
注意:如果您没有看到任何事务错误,则必须再次重新导入电影数据库并重新运行上述步骤。
因此,从我所见,删除失败,因为它尝试删除 Actor 节点及其所有 ACTS_IN 关系,它首先执行 MATCH 查询 (MATCH (n:Actor {name:\"Claude Jade\"}) OPTIONAL MATCH (n)-[r]-()
) 但在执行 DELETE n, r
之前,第二个进程设法插入新的 ACTS_IN 关系到 Actor 节点,这就是删除失败的原因,因为当它尝试执行 DELETE 时,Actor 已经添加了一个新关系。
我想知道Neo4J上是否有锁定机制可以用来防止这个问题?
最佳答案
您正在观察一个经典的竞争条件。为了防止这种情况,您需要获取 Claude
节点上的锁。 Cypher 没有用于显式获取锁的语法,因此我们只需将一个假属性设置为第一个操作,然后在最后删除该属性 - 这会产生获取锁的副作用。所以改变你的两个陈述:
MATCH (n:Actor {name: "Claude Jade"})
SET n._fake = 1 // grabs the lock as first action
WITH n
OPTIONAL MATCH (n)-[r]-()
DELETE n, r
MATCH (n:Actor {name: "Claude Jade"})
SET n._fake = 1 // grab the lock early
CREATE (n)-[:ACTS_IN]->(m:Movie {title: "Hello World"})
REMOVE n._fake // get rid of fake property
RETURN m
关于neo4j - Neo4J 上的锁定机制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33001468/