了解查询计划缓存
当 Cypher 语句首次提交时,Neo4j 会尝试在规划之前确定查询是否已在计划缓存中。默认情况下,Neo4j 会根据 conf/neo4j.conf 参数 dbms.query_cache_size 将 1000 个查询计划保存在缓存中。事实上这实际上代表了 2 个查询计划缓存。
-
字符串缓存
当 Cypher 最初提交时,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) 算法。因此,如果第一个加入计划缓存的查询每秒运行一次,而第二个加入的查询每两分钟运行一次,当需要移除缓存中的某个查询计划以容纳新查询时,会先移除第二个查询,因为第一个查询的调用频率更高。
最后,需要说明的是,任何模式更改,例如索引/约束的创建或删除,都会清空整个查询计划缓存。
此页面有帮助吗?