neo4j CREATE UNIQUE似乎很慢

时间:2016-10-30 23:19:19

标签: neo4j

我正在尝试创建一系列关系,其中Foo是'IN'栏中的一系列时间范围。

我的基本查询看起来像这样:

MERGE (f:Foo {id: 123})
MERGE (b111:Bar {id: 111})
CREATE UNIQUE (f) - [:IN { from:130958270580000000, to: 130958975440000000 } ] -> (b111)

这在功能上正是我所追求的,但是当我添加可能创建语句时,查询变得非常慢。循环这句话也很慢。

例如:

MERGE (f:Foo {id: 123})
MERGE (b111:Bar {id: 111})
MERGE (b222:Bar {id: 222})
CREATE UNIQUE (f) - [:IN { from:130958270580000000, to: 130958975440000000 } ] -> (b111)
CREATE UNIQUE (f) - [:IN { from:130954640800000000, to: 130954728070000000 } ] -> (b111)
CREATE UNIQUE (f) - [:IN { from:130954563680000000, to: 130954563920000000 } ] -> (b111)
CREATE UNIQUE (f) - [:IN { from:130954559880000000, to: 130954559900000000 } ] -> (b111)
CREATE UNIQUE (f) - [:IN { from:130954557300000000, to: 130954559300000000 } ] -> (b111)
CREATE UNIQUE (f) - [:IN { from:130954556860000000, to: 130954557100000000 } ] -> (b111)
CREATE UNIQUE (f) - [:IN { from:130953825060000000, to: 130954554060000000 } ] -> (b111)
CREATE UNIQUE (f) - [:IN { from:130953080610000000, to: 130953807160000000 } ] -> (b111)
CREATE UNIQUE (f) - [:IN { from:130948659890000000, to: 130952852200000000 } ] -> (b111)
CREATE UNIQUE (f) - [:IN { from:130947989650000000, to: 130948493470000000 } ] -> (b111)
CREATE UNIQUE (f) - [:IN { from:130947129060000000, to: 130947675200000000 } ] -> (b222)

例如,如果我创建400个这样的关系需要23秒

我为上面的内容运行了PROFILE,看起来它正在为这个语句执行大量的db命中(在158毫秒内总共有586个db命中率)。这看起来很奇怪。当我添加更多CREATE语句时,这似乎呈指数级增长。

我也试过向Foo和Bar添加索引,但它们似乎没有任何区别。

我是neo4j的新手,所以我可能会做某些愚蠢的事情或在某处做出错误的假设,但我无法解决为什么这个查询应该这么慢。

1 个答案:

答案 0 :(得分:3)

减速不是在您的节点上匹配,而是来自您的关系上的CREATE UNIQUE(MERGE也会遭受同样的缓慢)。归结为neo4j中的非索引属性访问可能很昂贵。

关系上没有索引,因此任何CREATE UNIQUE或MERGE操作都必须扫描该类型的所有关系并比较属性值以查看该关系是否已存在。显然,成本会随着存在的那种关系的数量而增长。

有几种方法可以缓解这个问题。

如果您知道这些与这些特定属性的关系尚不存在,只需使用CREATE而不是CREATE UNIQUE。您可以运行查询以在关系创建结束时进行检查,如果您在任何地方搞错并且可以删除重复项。

另一种方法是调整您的模型。不是在关系本身上存在属性,而是在:Foo和:Bar节点之间创建一个具有自己标签的中间节点,并使用它来保存from和to属性。您需要索引这些属性以避免MERGE或CREATE UNIQUE减速。

我建议使用一个中间节点,特别是如果您计划使用涉及这些时间属性的查询,并且肯定有很多这样的时间属性。如果这些属性仅用于关系,则无法利用索引来加快查询速度,这可能会对您造成问题。

修改

中间节点的用法可能如下所示(假设索引为:Foobar(from)和:Foobar(to)):

MERGE (f:Foo {id: 123})
MERGE (f2:Foo {id: 456})
MERGE (b111:Bar {id: 111})
MERGE (b222:Bar {id: 222})
MERGE (f) - [:IN] -> (fb:Foobar{ from:130958270580000000, to: 130958975440000000 })
MERGE (fb) - [:IN] -> (b111)
WITH f, f2, b111, b222
// merge with same :Foobar values on f2...should create a new node
// instead of reusing the one attached to f.
MERGE (f2) - [:IN] -> (fb:Foobar{ from:130958270580000000, to: 130958975440000000 })
MERGE (fb) - [:IN] -> (b111)
WITH f, f2, b111, b222
MERGE (f) - [:IN] -> (fb:Foobar{ from:130954640800000000, to: 130954728070000000 })
MERGE (fb) - [:IN] -> (b111)
WITH f, f2, b111, b222
MERGE (f) - [:IN] -> (fb:Foobar{ from:130954563680000000, to: 130954563920000000 })
MERGE (fb) - [:IN] -> (b111)
...

当然,如果可能,您可能需要将您的关系和标签重命名为更合理的内容。

使用此查询的查询可能如下所示:

// find which :Bar f was in at a particular instance
WITH {params.instance} as instance
MATCH (f:Foo {id: 123})-[:IN]->(fb:Foobar)
WHERE fb.from <= instance <= fb.to
WITH fb
MATCH (fb)-[:IN]->(b:Bar)
RETURN b

EDIT改变插入查询以强制执行:Foobar节点的唯一性到相应的:Foo节点,这将阻止MERGE在现有的Foobar节点上匹配:另一个已经使用的Foobar节点:Foo。