上周,我花了很多时间尝试使用实时系统中的数据来优化大约20个执行失败的Cypher查询(36866ms至155575ms)。 经过一番尝试和错误,以及来自Michael的大量投入,我能够大致确定对查询进行哪些操作才能使它们性能更好-最后,性能最差的查询降至521ms冷图和1GB堆空间(并且该查询具有可选路径-不确定如何改进),其余都在50ms以下-与原始数字相比有很大改进。 希望这可能对其他人有所帮助,这就是我所做的事情(大多数是猜测工作,并且在很大程度上不科学)-也许Michael可以帮助解释内部原理,并根据某些假设对我进行纠正。 我要做的第一件事是确保每个密码查询都使用参数-如Neo4j文档中所述 ,这有助于缓存执行计划。

其次,我遇到了Neo4j邮件列表中的一则帖子,其中Michael提到不要重新实例化ExecutionEngine,以便上述参数化查询实际上可以缓存。 对于许多人来说,这似乎很明显,但这是一个容易被忽视的事实,因为我有一个名为QueryExecutor的类,该类包含一个执行带有参数映射的查询的方法,并且该方法为每个查询创建了一个新的ExecutionEngine。 一旦编写并多次使用了此方法,就很容易忘记。 但是,这是影响整体性能的一个非常重要的因素(文档中的提及将非常有帮助),它解释了为什么即使在参数化的情况下,我的查询通常也需要花费相同的时间来执行。 将其更改为使用缓存的ExecutionEngine,可以看到我的查询时间工作表的下半部分在缓存后下降了0到1毫秒,这是非常出色的进步。

现在,从最坏的情况开始进入每个查询。 我决定在本地计算机上进行优化,仅分配1GB的堆空间,并在一个冷图上进行优化。 因此,我忽略了缓存后查询执行的改进-我认为这是确保进度的一种更好的方法-如果第一个查询命中没有改善,那么您确实没有对其进行优化。 这样,如果它在有限的硬件上确实能很好地工作,我对它在生产中可以更好地工作充满信心。

除了在代码中安排查询时间之外,我还使用webadmin控制台进行了优化。 该查询糟糕的指标是它不会返回并且控制台将挂起。 优化以使其不会挂起本身就是一个重大改进。 高度不科学,但我建议这样做。

我的第一个查询平均大约76558毫秒(该时间是通过在引擎execute方法周围添加开始时间和结束时间而获得的)。 第一次优化后,它减少到466ms。 这是原始查询: https : //gist.github.com/4436272

这是经过优化的一个: https ://gist.github.com/4436281无需执行此大型匹配只是为了基于a的alertDate属性过滤出结果,因此我减少了匹配以返回最小的集合首先可以过滤的数据,即 通往的道路 如果要在第一次匹配之前执行错误查询,则会看到返回2万行之类的信息。 过滤后,它们只有450个奇数行。 可以想象,第二个查询Swift减少了您可能使用的结果数量。

第二个变化是我那天从迈克尔那里学到的东西,当时我问做大型比赛还是继续修剪子查询是否有意义。 他的回答是:“ 问题是,您是在使用match来通过描述模式来实际扩展结果集,还是只是想确定某些关系存在(或不存在),即过滤。 如果是后者,则可能要在where子句中使用path-expression语法。

WHERE a-[:FOO]->b
or WHERE NOT(a-[:FOO]->b)'

这花了一点时间来适应,因为我编写MATCH子句的方式恰恰是我脑海中的想法,但是现在我可以区分出所需结果的匹配项与过滤器的匹配项了。 在上面的查询中,我的结果中需要(ir),因此无需在匹配中包含(a)-[:alert_for_inspection]->(i); 我可以在WHERE中使用它来确保a确实与i有关。

这是另一个示例: https : //gist.github.com/4436293

立刻,您会看到我们按日期过滤了cm关系-如果它们不在日期范围内,则无需我们先进行匹配。 因此,查询的这一部分可以重写为:

start c=node:companies(id={id})
match c <-[parent_company*0..]-(n)

with n
match n-[:defines_business_process]->(bp)-[:has_cycle]->(cm)
where cm.measureDate>={measureStartDate} and cm.measureDate<={measureEndDate}

在那之后,下一个过滤器处于相同的原理:

with cm
match (cm)-[:cycle_metric] ->m-[:metric_activity] ->ma-[:metric_unit] -> (u)-[:alert_for_unit]-(a) where a.alertDate=cm.measureDate and a.fromEntityType={type}

这进一步修剪了我们的结果集。 最后,将连接添加到r(以得到我们的结果),确保不会导致r产生但必不可少的路径进入WHERE子句:

with a,ma
match (r) < - [:for_inspection_result]-a-[:alert_for_inspection]- > i
where (i) < -[:metric_inspection]-(ma)
return a.id as alertId, r.value as resultValue

这是完整的查询: https ://gist.github.com/4436328原始时间-33360毫秒,经过优化-246毫秒。

至少对我来说,我的大多数查询都属于这种模式,因此,到第二天,我就能够非常快速地重构它们。 有趣的是,在此之后,我仍然感到响应缓慢,但是在日志中打印的查询时间很小。 消除之后,我发现我的代码在执行查询之后(由executionEngine.execute)实际上停留了很长时间,但在结果集的第一次迭代中。我假设结果不一定在execute()方法期间收集,但在结果集的迭代中比较懒惰-我不了解Cypher内部结构,因此我可能完全错了。 但是定时迭代本身可以指出更差的查询。

其他点点滴滴-ORDER BY增加了很多时间。 如果没有它,您应该放下第一件事。 DISTINCT还增加了时间,但是在我的许多情况下,很难删除它。

如果要检查是否缺少可选路径,通常在哪里进行MATCH(u)-[r?:has_a]-(a)WHERE NOT(r为null),而是改写为MATCH(u)-[ other_stuff]-.. WHERE NOT(u-[:has_a] -a)的效果要好得多。 但是,在我有诸如MATCH X- [o?:optional] -Y WHERE(存在o,将Y匹配到A和B)或OR(不存在o,将X匹配到c和d)的可选路径的地方,我无法简化与没有可选路径的其他查询相比,这些查询仍需要一些时间。

最终-该问题被发现得太晚了,因为测试数据从未与实时数据非常接近。 图的结构起着很大的作用-一些节点是紧密连接的,而其他节点则不是很多-并且涉及那些紧密连接的节点的查询是对我们最大的伤害。 尽可能尝试使用生产质量数据进行性能测试,或者尝试创建与之类似的测试数据。

因此,总结一下:

  1. 始终参数化您的查询
  2. 缓存ExecutionEngine
  3. 找出要过滤的内容,并尽早在涉及的匹配最少的情况下应用该过滤器,以便在进一步查询时结果集逐渐变小。 继续测量每个子查询中返回的时间和结果,以便您可以确定过滤器不明显时先执行的操作
  4. 检查您的MATCH和RETURN子句-在MATCH中仅包括RETURN所需的那些部分。 剩下的将用于过滤结果的信息可以进入WHERE
  5. 如果您不需要ORDER BY,请昨天放下
  6. 如果您不需要DISTINCT,也要摆脱它
  7. 如果不需要基于可选路径的检查,可以将检查是否存在可选路径从MATCH移到WHERE
  8. 不仅要花费查询的execute(),还要花费时间迭代结果
  9. 如果您的Webadmin控制台挂起,则说明您做的不好。 删除查询的各个部分以找出有问题的部分。
  10. 尝试尽可能使用实时数据
  11. 在资源不佳的冷图上进行测试-当您看到生产中的图形滑过您时,您会感觉好多了!

参考: Thought Bytes博客上来自我们JCG合作伙伴 Aldrin和Luanne Misquitta的优化Neo4j Cypher查询 。

翻译自: https://www.javacodegeeks.com/2013/01/optimizing-neo4j-cypher-queries.html