探索查询执行摘要

当查询的所有结果都被处理后,服务器通过返回一个执行摘要来结束事务。它是一个 ResultSummary 对象,其中包含以下信息:

  • 查询计数器 — 查询在服务器上触发了哪些更改

  • 查询执行计划 — 数据库将如何(或已如何)执行查询

  • 通知 — 服务器在运行查询时发出的额外信息

  • 时间信息和查询请求摘要

获取执行摘要

使用 ExecuteQuery() 运行查询时,执行摘要是默认返回对象的一部分,位于 Summary 键下。

result, _ := neo4j.ExecuteQuery(ctx, driver, `
    UNWIND ["Alice", "Bob"] AS name
    MERGE (p:Person {name: name})
    `, nil,
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
summary := result.Summary

如果您正在使用事务函数,您可以使用 Result.Consume() 方法获取查询执行摘要。请注意,一旦您请求执行摘要,结果流将被耗尽。这意味着任何尚未处理的记录都将被丢弃。

session := driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: "neo4j"})
defer session.Close(ctx)
summary, _ := session.ExecuteWrite(ctx,
    func(tx neo4j.ManagedTransaction) (any, error) {
        result, err := tx.Run(ctx, `
            UNWIND ["Alice", "Bob"] AS name
            MERGE (p:Person {name: name})
            `, nil)
        summary, _ := result.Consume(ctx)
        return summary, err
    })

查询计数器

方法 ResultSummary.Counters() 返回查询触发的操作的计数器(作为一个 Counters 对象)。

插入一些数据并显示查询计数器
result, _ := neo4j.ExecuteQuery(ctx, driver, `
    MERGE (p:Person {name: $name})
    MERGE (p)-[:KNOWS]->(:Person {name: $friend})
    `, map[string]any{
        "name": "Mark",
        "friend": "Bob",
    }, neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
summary := result.Summary
counters := summary.Counters()
fmt.Println("Nodes created:", counters.NodesCreated())
fmt.Println("Labels added:", counters.LabelsAdded())
fmt.Println("Properties set:", counters.PropertiesSet())
fmt.Println("Relationships created:", counters.RelationshipsCreated())

// Nodes created: 2
// Labels added: 2
// Properties set: 2
// Relationships created: 1

还有两个额外的布尔方法充当元计数器

  • .ContainsUpdates() — 查询是否在其运行的数据库上触发了任何写入操作

  • .ContainsSystemUpdates() — 查询是否更新了 system 数据库

查询执行计划

如果您在查询前加上 EXPLAIN,服务器将返回它用于运行查询的计划,但实际上并不会运行它。您可以通过调用 ResultSummary.Plan() 来获取该计划,其中包含将用于检索结果集的 Cypher 运算符列表。您可以使用此信息来查找潜在的瓶颈或性能改进空间(例如通过创建索引)。

result, _ := neo4j.ExecuteQuery(ctx, driver,
    "EXPLAIN MATCH (p {name: $name}) RETURN p",
    map[string]any{
        "name": "Alice",
    },
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
fmt.Println(result.Summary.Plan().Arguments()["string-representation"])
/*
Planner COST
Runtime PIPELINED
Runtime version 5.0
Batch size 128

+-----------------+----------------+----------------+---------------------+
| Operator        | Details        | Estimated Rows | Pipeline            |
+-----------------+----------------+----------------+---------------------+
| +ProduceResults | p              |              1 |                     |
| |               +----------------+----------------+                     |
| +Filter         | p.name = $name |              1 |                     |
| |               +----------------+----------------+                     |
| +AllNodesScan   | p              |             10 | Fused in Pipeline 0 |
+-----------------+----------------+----------------+---------------------+

Total database accesses: ?
*/

如果您改为在查询前加上关键字 PROFILE,服务器将返回它已用于运行查询的执行计划,以及分析器统计信息。这包括所使用的运算符列表和每个中间步骤的额外分析信息。您可以通过调用 ResultSummary.Profile() 来访问该计划。请注意,该查询也会运行,因此结果对象也包含任何结果记录。

result, _ := neo4j.ExecuteQuery(ctx, driver,
    "PROFILE MATCH (p {name: $name}) RETURN p",
    map[string]any{
        "name": "Alice",
    },
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))
fmt.Println(result.Summary.Profile().Arguments()["string-representation"])
/*
Planner COST
Runtime PIPELINED
Runtime version 5.0
Batch size 128

+-----------------+----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+
| Operator        | Details        | Estimated Rows | Rows | DB Hits | Memory (Bytes) | Page Cache Hits/Misses | Time (ms) | Pipeline            |
+-----------------+----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+
| +ProduceResults | p              |              1 |    1 |       3 |                |                        |           |                     |
| |               +----------------+----------------+------+---------+----------------+                        |           |                     |
| +Filter         | p.name = $name |              1 |    1 |       4 |                |                        |           |                     |
| |               +----------------+----------------+------+---------+----------------+                        |           |                     |
| +AllNodesScan   | p              |             10 |    4 |       5 |            120 |                 9160/0 |   108.923 | Fused in Pipeline 0 |
+-----------------+----------------+----------------+------+---------+----------------+------------------------+-----------+---------------------+

Total database accesses: 12, total allocated memory: 184
*/

有关更多信息和示例,请参阅基本查询调优

通知

执行查询后,服务器可以与查询结果一同返回通知。通知包含性能改进建议、关于使用已弃用功能的警告以及关于 Neo4j 次优使用的其他提示。

对于驱动程序版本 >= 5.25 和服务器版本 >= 5.23,提供两种形式的通知(Neo4j 状态码GQL 状态码)。对于早期版本,仅提供Neo4j 状态码
GQL 状态码计划取代 Neo4j 状态码。
示例 1. 无限最短路径引发性能通知

方法 Summary.Notifications() 返回一个 Notification 对象列表。

result, _ := neo4j.ExecuteQuery(ctx, driver, `
    MATCH p=shortestPath((:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'}))
    RETURN p
    `, nil,
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))

for _, notification := range result.Summary.Notifications() {
    fmt.Println("Code:", notification.Code())
    fmt.Println("Title:", notification.Title())
    fmt.Println("Description:", notification.Description())
    fmt.Println("Severity:", notification.SeverityLevel())
    fmt.Println("Category:", notification.Category(), "\n")
}
/*
Code: Neo.ClientNotification.Statement.UnboundedVariableLengthPattern
Title: The provided pattern is unbounded, consider adding an upper limit to the number of node hops.
Description: Using shortest path with an unbounded pattern will likely result in long execution times. It is recommended to use an upper limit to the number of node hops in your pattern.
Severity: INFORMATION
Category: PERFORMANCE
*/

对于版本 >= 5.25,方法 Summary.GqlStatusObjects() 返回一个 GqlStatusObject 列表。这些是符合 GQL 的状态对象。

有些(但不是所有)GqlStatusObjects 是通知,而有些则报告一个结果状态:00000 表示“成功”,02000 表示“无数据”,00001 表示“省略结果”。Summary.GqlStatusObjects() 始终包含至少一个条目,其中包含结果状态。

result, _ := neo4j.ExecuteQuery(ctx, driver, `
    MATCH p=shortestPath((:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'}))
    RETURN p
    `, nil,
    neo4j.EagerResultTransformer,
    neo4j.ExecuteQueryWithDatabase("neo4j"))

for _, status := range result.Summary.GqlStatusObjects() {
    fmt.Println("GQLSTATUS:", status.GqlStatus())
    fmt.Println("Description:", status.StatusDescription())
    // Not all statuses are notifications.
    fmt.Println("Is notification:", status.IsNotification())

    // Notification and thus vendor-specific fields.
    // These fields are only meaningful for notifications.
    if status.IsNotification() == true {
        fmt.Println("Position (offset, line, column):", status.Position().Offset(), status.Position().Line(), status.Position().Column())
        fmt.Println("Classification:", status.Classification())
        fmt.Println("Unparsed classification:", status.RawClassification())

        fmt.Println("Severity:", status.Severity())
        fmt.Println("Unparsed severity:", status.RawSeverity())
    }
    // Any raw extra information provided by the server
    fmt.Println("Diagnostic record:", status.DiagnosticRecord())
    fmt.Println(strings.Repeat("=", 80))
}
/*
GQLSTATUS: 02000
Description: note: no data
Is notification: false
Diagnostic record: map[CURRENT_SCHEMA:/ OPERATION: OPERATION_CODE:0]
================================================================================
GQLSTATUS: 03N91
Description: info: unbounded variable length pattern. The provided pattern `(:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'})` is unbounded. Shortest path with an unbounded pattern may result in long execution times. Use an upper limit (e.g. `[*..5]`) on the number of node hops in your pattern.
Is notification: true
Position (offset, line, column): 26 2 26
Classification: PERFORMANCE
Unparsed classification: PERFORMANCE
Severity: INFORMATION
Unparsed severity: INFORMATION
Diagnostic record: map[CURRENT_SCHEMA:/ OPERATION: OPERATION_CODE:0 _classification:PERFORMANCE _position:map[column:26 line:2 offset:26] _severity:INFORMATION _status_parameters:map[pat:(:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'})]]
================================================================================
*/

过滤通知

默认情况下,服务器会分析每个查询的所有类别和严重程度的通知。从版本 5.7 开始,您可以使用参数 NotificationsMinSeverity 和/或 NotificationsDisabledCategories/NotificationsDisabledClassifications 来限制您感兴趣的通知的严重程度和/或类别/分类。限制服务器允许发出的通知数量会带来轻微的性能提升。

严重性过滤器适用于 Neo4j 和 GQL 通知。类别和分类过滤器独立存在仅是由于 GQL 和 Neo4j 之间词汇的差异;但这两种过滤器都会影响任何形式的通知,因此您应该只使用其中一个。您可以在创建 Driver 实例或创建会话时使用这些参数中的任何一个。

通过将最低严重性设置为 "OFF",您可以完全禁用通知。

只允许 Warning 通知,但不能是 HintGeneric 类别
// import (
//     "github.com/neo4j/neo4j-go-driver/v5/neo4j/notifications"
//     "github.com/neo4j/neo4j-go-driver/v5/neo4j/config"
// )

// At driver level
driverNot, _ := neo4j.NewDriverWithContext(
    dbUri,
    neo4j.BasicAuth(dbUser, dbPassword, ""),
    func (conf *config.Config) {
        conf.NotificationsMinSeverity = notifications.WarningLevel  // or "OFF" to disable entirely
        conf.NotificationsDisabledClassifications = notifications.DisableClassifications(notifications.Hint, notifications.Generic)  // filters categories as well
    })


// At session level
sessionNot := driver.NewSession(ctx, neo4j.SessionConfig{
    NotificationsMinSeverity: notifications.WarningLevel,  // or "OFF" to disable entirely
    NotificationsDisabledClassifications: notifications.DisableClassifications(notifications.Hint, notifications.Generic),  // filters categories as well
    DatabaseName: "neo4j",  // always provide the database name
})
© . All rights reserved.