图查询最佳实践
本文档介绍了优化 BigQuery Graph 查询的最佳实践。
从基数较低的节点开始路径遍历
为了保持较小的中间结果集并加快查询执行速度,请编写图查询,使路径遍历从基数较低的节点开始,无论路径遍历的方向如何。以下 MATCH 语句使用属性过滤条件来减少可能的起始节点数量,而不是先计算所有匹配项,然后再进行过滤:
MATCH (p:Person {id: 10})-[own:Owns]->(a:Account)
MATCH (a:Account WHERE balance > 10)<-[own:Owns]-(p:Person)
对于量化路径查询,这一点尤为重要:
MATCH (p:Person {id: 10})-[own:Owns]->{1,3}(a:Account)
使用 ANY 或 ANY SHORTEST 语法进行连接检查
量化路径查询可能会返回来源节点和目标节点之间的重复路径。如果您的目标是检查连接性,并且不需要所有可能的路径,请使用 ANY 或 ANY SHORTEST 来减少冗余计算并提高路径查找效率。例如,以下 MATCH 语句使用 ANY SHORTEST 仅保留每对节点之间的一条路径:
MATCH ANY SHORTEST (a1:Account)-[t:Transfers]->{1,3}(a2:Account)
使用定向路径遍历
BigQuery 图架构是有方向的,这意味着每条边都有一个源节点和一个目标节点。虽然图查询语法允许在任何方向进行路径遍历(例如 -[edge]-),但我们建议使用定向路径遍历(例如 -[edge]-> 或 <-[edge]-)以获得更好的性能。任何方向的路径遍历都可能会导致性能下降。
以下 MATCH 语句使用任意方向的路径遍历:
-- Avoid.
MATCH (a1:Account {id: 7})-[t:Transfers]-(a2:Account)
改为使用 UNION ALL 组合两个方向性遍历:
MATCH (a1:Account {id: 7})-[t:Transfers]->(a2:Account)
...
UNION ALL
...
MATCH (a1:Account {id: 7})<-[t:Transfers]-(a2:Account)
明确指定标签
如果查询中省略了节点或边标签,BigQuery Graph 会枚举所有符合条件的节点和边标签。此枚举可能会导致扫描的标签多于必要数量。为避免这种情况,请尽可能在查询中为所有节点和边指定标签。
例如,以下查询指定了 Account 和 Transfers 标签:
GRAPH graph_db.FinGraph
MATCH (a1:Account)-[t:Transfers]->(a2:Account)
RETURN COUNT(*) AS num_transfers;
避免省略标签,因为这可能会扫描节点之间的其他不必要的关系。在以下查询中,a1 可以表示账号或个人,而 t 可以表示转移或账号所有权。
GRAPH graph_db.FinGraph
MATCH (a1)-[t]->(a2)
RETURN COUNT(*) AS num_transfers;
首选单个 MATCH 语句
借助 BigQuery 图,您可以在单个图查询中包含多个 MATCH 语句。这些语句通过表示同一节点或边的多声明变量连接在一起。不过,使用多个 MATCH 语句可能会降低语句之间的基数优势。请尽量使用单个 MATCH 语句,以提高性能。
例如,以下查询是等效的,但第一个查询的执行效果更好,因为它只使用了一个 MATCH 语句:
-- Preferred syntax.
GRAPH graph_db.FinGraph
MATCH
(p:Person {id: 1})-[o:Owns]->
(a:Account)-[t:Transfers]->(a2:Account)
RETURN o.account_id, t.amount;
-- Avoid this syntax.
GRAPH graph_db.FinGraph
MATCH (p:Person {id: 1})-[o:Owns]->(a:Account)
MATCH (a:Account)-[t:Transfers]->(a2:Account)
RETURN o.account_id, t.amount;
限制从高基数节点遍历的边数
查询图时,某些节点的入边或出边数量可能远大于其他节点。这些高基数节点有时称为超级节点或中心节点。超级节点可能会导致性能问题,因为通过它们进行的遍历可能涉及处理大量数据,从而导致数据倾斜和执行时间过长。
如需优化包含超级节点的图的查询,请在 MATCH 的 FILTER 子句或 WHERE 子句中使用 ROW_NUMBER() 函数,以限制查询从节点遍历的边的数量。如果您不需要完整枚举超级节点的所有连接,则此方法特别有用。
例如,如果 FinGraph 中的某些账号有大量交易,您可以使用 ROW_NUMBER() 限制每个 Account 要考虑的 Transfers 边的数量,从而避免查询效率低下:
GRAPH graph_db.FinGraph
MATCH (a1:Account)-[e1:Transfers WHERE e1 IN {
GRAPH graph_db.FinGraph
-- Sample 5 edges per source node
MATCH -[selected_e:Transfers]->
FILTER ROW_NUMBER() OVER (
PARTITION BY SOURCE_NODE_ID(selected_e)) < 5
RETURN selected_e
}]->{1,3}(a2:Account)
RETURN COUNT(*) AS cnt;
对多跳查询中的中间节点或边缘进行采样
您还可以通过使用 ROW_NUMBER() 对多跳查询中的中间节点进行采样来提高查询效率。此方法通过限制查询为每个中间节点考虑的路径数量来提高效率。为此,请将多跳查询拆分为多个以 NEXT 分隔的 MATCH 语句,并在需要采样的中间点应用 ROW_NUMBER():
GRAPH graph_db.FinGraph
MATCH (a1:Account)-[e1:Transfers]->(a2:Account)
-- Sample 5 destination nodes per a1 source node
FILTER ROW_NUMBER() OVER (PARTITION BY ELEMENT_ID(a1)) < 5
RETURN a1, a2
NEXT
MATCH (a2)-[e2:Transfers]->(a3:Account)
RETURN a1.id AS src_id, a2.id AS mid_id, a3.id AS dst_id;