理解查询计划缓存
当 Cypher 语句首次提交时,Neo4j 会尝试在规划之前确定查询是否在计划缓存中。默认情况下,Neo4j 会根据 conf/neo4j.conf
参数 dbms.query_cache_size 在缓存中保留 1000 个查询计划。实际上,这代表了两个查询计划缓存。
-
字符串缓存
当 Cypher 语句最初提交时,会对其字符串按原样计算哈希值。使用这个哈希值,我们将尝试确定语句是否已存在于计划缓存中,如果存在,则可能无需重新规划。
然而请注意,逻辑上相同但大小写不同的语句会生成不同的哈希值。以下两个语句,尽管语义上等效,但会生成不同的哈希值,并且可能需要重新规划。
match (n) return count(n);
MATCH (n) return COUNT(n);
此外,逻辑上相同但空格/回车符不同的语句会生成不同的哈希值。以下三个语句将生成不同的哈希值,并且可能需要重新规划。
MATCH (n) return COUNT(n);
MATCH (n) return COUNT(n);
MATCH (n)
return COUNT(n);
以 PROFILE/EXPLAIN
开头的 Cypher 语句在进行哈希处理之前会移除其 PROFILE/EXPLAIN
。以下两个语句将哈希到相同的值。
MATCH (n) return COUNT(n);
PROFILE MATCH (n) return COUNT(n);
如果在第一个缓存中未找到 Cypher 语句的哈希值,Neo4j 将尝试确定它是否在第二个缓存中。
-
AST 缓存
Neo4j 编译器将查询从字符串解析为抽象语法树 (AST),即查询的对象表示。然后优化器会规范化语句,以便于规划。例如:
match (n:Person {id:101}) return n;
将被规范化为:
match (n:Person) where n.id={param1} return n; {param1: 101}
在这种情况下,Neo4j 将谓词 {id:101}
从 MATCH
模式移到 WHERE
子句中,并将值 101
参数化为一个参数,例如 n.id={param1}
。参数的使用在此处有更详细的说明:此处
AST 不存储诸如空格和关键字大小写等信息,并且由于它已经被参数化,字面值可以改变但仍然生成相同的 AST。
这个第二个查询缓存以这个规范化的 AST 为键。也就是说,这些查询将重用相同的查询计划。
match (n:Person) where n.id=101 return n;
match (n:Person {id:101}) return n;
MATCH ( n:Person { id : 101 } )
RETURN n;
最后,如果 Cypher 语句在第一个或第二个缓存中找到,查询仍可能根据 conf/neo4j.conf
参数 cypher.min_replan_interval 和 cypher.statistics_divergence_threshold 进行重新规划。
cypher.min_replan_interval 用于定义缓存计划存在多长时间后才有资格重新规划,默认值为 10 秒。
cypher.statistics_divergence_threshold 用于指示 Cypher 使用的底层数据的统计信息变化了多少百分比。默认值是 0.75,这意味着如果对象中的统计信息自上次生成缓存计划以来变化了超过 75%,则需要生成一个新的计划。例如运行:
// remove all :Person nodes
match (n:Person) detach delete n;
// create 10 :Person nodes
foreach (x in range (1,10) | create (n:Person {id:x}));
// list the 10 :Person nodes created
match (n:Person) return n.id order by n.id desc;
// create 8 new :Person nodes
foreach (x in range (11,18) | create (n:Person {id:x}));
// list the 18 :Person nodes
match (n:Person) return n.id order by n.id desc;
两个 match (n:Person) return n.id order by n.id desc;
都将被规划,特别是第二个实例,尽管具有相同的哈希值,但 :Person
的统计信息已从 10 个节点更改为 18 个节点,从而超过了 75% 的变化。
如果由于上述两个参数导致现有计划需要重新规划,logs/debug.log
将记录:
2017-03-31 19:14:27.820+0000 INFO [o.n.c.i.ExecutionEngine] Discarded stale query from the query cache: match (n:Person) return n.id order by n.id desc; 2017-03-31 19:14:27.821+0000 INFO [o.n.c.i.EnterpriseCompatibilityFactory] Discarded stale query from the query cache: match (n:Person) return n.id order by n.id desc;
此外,需要注意的是,当查询计划从缓存中移除以便为新计划腾出空间时,采用的是最不常用 (LFU) 算法。因此,如果第一个添加到计划缓存的查询每 1 秒运行一次,而第二个添加到查询计划缓存的查询每 2 分钟添加一次,那么当我们需要从缓存中移除查询计划以为新查询腾出空间时,我们会先移除第二个查询而不是第一个查询,因为第一个查询被调用的频率更高。
最后,应该注意的是,任何模式更改,例如索引/约束的创建/移除,都将刷新整个查询计划缓存。
此页面有帮助吗?