Explore the query execution summary
After all records of a query result have been processed, the server ends the transaction by returning a summary of execution.
It comes as a ResultSummary
object, and it contains information among which:
-
Query counters — What changes the query triggered on the server
-
Query execution plan — How the database would execute (or executed) the query
-
Notifications — Extra information raised by the server while running the query
-
Timing information and query request summary
Retrieve the execution summary
When running queries with Driver.executableQuery()
, the execution summary is part of the default return object, retrievable through the .summary()
method.
var result = driver.executableQuery("""
UNWIND ['Alice', 'Bob'] AS name
MERGE (p:Person {name: name})
""")
.withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
.execute();
var resultSummary = result.summary();
If you are using transaction functions, you can retrieve the query execution summary with the method Result.consume()
.
Notice that once you ask for the execution summary, the result stream is exhausted: any record yet to be processed is not available anymore.
try (var session = driver.session(SessionConfig.builder().withDatabase("<database-name>").build())) {
var resultSummary = session.executeWrite(tx -> {
var result = tx.run("""
UNWIND ['Alice', 'Bob'] AS name
MERGE (p:Person {name: name})
""");
return result.consume();
});
}
Query counters
The method ResultSummary.counters()
returns counters for the operations that a query triggered (as a SummaryCounters
object).
var result = driver.executableQuery("""
MERGE (p:Person {name: $name})
MERGE (p)-[:KNOWS]->(:Person {name: $friend})
""").withParameters(Map.of("name", "Mark", "friend", "Bob"))
.withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
.execute();
var queryCounters = result.summary().counters();
System.out.println(queryCounters);
/*
InternalSummaryCounters{nodesCreated=2, nodesDeleted=0, relationshipsCreated=1, relationshipsDeleted=0,
propertiesSet=2, labelsAdded=2, labelsRemoved=0, indexesAdded=0, indexesRemoved=0, constraintsAdded=0,
constraintsRemoved=0, systemUpdates=0}
*/
Two additional boolean methods act as meta-counters:
-
.containsUpdates()
— Whether the query triggered any write operation on the database on which it ran -
.containsSystemUpdates()
— Whether the query triggered any write operation on thesystem
database
Query execution plan
If you prefix a query with EXPLAIN
, the server returns the plan it would use to run the query, but doesn’t actually run it.
The plan is then available as a Plan
object through the method ResultSummary.plan()
, and contains the list of Cypher operators that would be used to retrieve the result set.
Use this information to locate bottlenecks or potential room for performance improvements (for example through the creation of indexes).
var result = driver.executableQuery("EXPLAIN MATCH (p {name: $name}) RETURN p")
.withParameters(Map.of("name", "Alice"))
.withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
.execute();
var queryPlan = result.summary().plan().arguments().get("string-representation");
System.out.println(queryPlan);
/*
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: ?
*/
If you instead prefix a query with the keyword PROFILE
, the server returns the execution plan it has used to run the query, together with profiler statistics.
This includes the list of operators that were used and additional profiling information about each intermediate step.
The plan is available as a Plan
object through the method ResultSummary.profile()
.
Notice that the query is also run, so the result object also contains any result records.
var result = driver.executableQuery("PROFILE MATCH (p {name: $name}) RETURN p")
.withParameters(Map.of("name", "Alice"))
.withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
.execute();
var queryPlan = result.summary().profile().arguments().get("string-representation");
System.out.println(queryPlan);
/*
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
*/
For more information and examples, see Basic query tuning.
Notifications
After executing a query, the server can return notifications alongside the query result. Notifications contain recommendations for performance improvements, warnings about the usage of deprecated features, and other hints about sub-optimal usage of Neo4j.
The method ResultSummary.gqlStatusObjects()
returns an ordered set of GQL-compliant status objects.
The set can contain both Notification
objects and GqlStatusObject
objects.
The latter encodes the query’s outcome status: 00000
for "success", 02000
for "no data", and 00001
for "omitted result".
The set always contains at least one entry, containing the outcome status.
var result = driver.executableQuery("""
MATCH p=shortestPath((:Person {name: $start})-[*]->(:Person {name: $end}))
RETURN p
""")
.withParameters(Map.of("start", "Alice", "end", "Bob"))
.withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
.execute();
var statuses = result.summary().gqlStatusObjects();
System.out.println(statuses);
/*
[
InternalGqlStatusObject{gqlStatus='02000', statusDescription='note: no data', diagnosticRecord={OPERATION_CODE="0", OPERATION="", CURRENT_SCHEMA="/"}},
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., severityLevel=InternalNotificationSeverity[type=INFORMATION, level=800], rawSeverityLevel=INFORMATION, classification=PERFORMANCE, rawClassification=PERFORMANCE, position={offset=21, line=1, column=22}
]
*/
Deprecated in 6.0
The method ResultSummary.notifications()
returns a list of Notification
objects.
var result = driver.executableQuery("""
MATCH p=shortestPath((:Person {name: $start})-[*]->(:Person {name: $end}))
RETURN p
""")
.withParameters(Map.of("start", "Alice", "end", "Bob"))
.withConfig(QueryConfig.builder().withDatabase("<database-name>").build())
.execute();
var notifications = result.summary().notifications();
System.out.println(notifications);
/*
[
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.,
severityLevel=InternalNotificationSeverity[type=INFORMATION,
level=800],
rawSeverityLevel=INFORMATION,
category=InternalNotificationCategory[type=PERFORMANCE],
rawCategory=PERFORMANCE,
position={offset=21, line=1, column=22}
]
*/
Filter notifications
By default, the server analyses each query for all categories and severity of notifications.
Use the configuration methods .withMinimumNotificationSeverity()
and .withDisabledNotificationClassification()
to tweak the severity and/or category/classification of notifications that you are interested into, or to disable them altogether.
There is a slight performance gain in restricting the amount of notifications the server is allowed to raise.
The severity filter applies to both Neo4j and GQL notifications. The classification filter acts on both categories and classifications.
You can call the methods both on a Config
object when creating a Driver
instance, and on a SessionConfig
object when creating a session.
WARNING
notifications, but not of HINT
or GENERIC
classifications// import java.util.Set
// import org.neo4j.driver.Config;
// import org.neo4j.driver.NotificationClassification;
// import org.neo4j.driver.NotificationConfig;
// import org.neo4j.driver.NotificationSeverity;
// import org.neo4j.driver.SessionConfig;
// at `Driver` level
var driver = GraphDatabase.driver(
dbUri, AuthTokens.basic(dbUser, dbPassword),
Config.builder()
.withMinimumNotificationSeverity(NotificationSeverity.WARNING) // NotificationSeverity.OFF to disable entirely
.withDisabledNotificationClassifications(Set.of(NotificationClassification.HINT, NotificationClassification.GENERIC)) // filters categories as well
.build()
);
// at `Session` level
var session = driver.session(
SessionConfig.builder()
.withDatabase("<database-name>")
.withMinimumNotificationSeverity(NotificationSeverity.WARNING) // NotificationSeverity.OFF to disable entirely
.withDisabledNotificationClassifications(Set.of(NotificationClassification.HINT, NotificationClassification.GENERIC)) // filters categories as well
.build()
);
// import org.neo4j.driver.Config;
// import org.neo4j.driver.NotificationSeverity;
// import org.neo4j.driver.SessionConfig;
// at `Driver` level
var driver = GraphDatabase.driver(
dbUri, AuthTokens.basic(dbUser, dbPassword),
Config.builder()
.withMinimumNotificationSeverity(NotificationSeverity.OFF)
.build()
);
// at `Session` level
var session = driver.session(
SessionConfig.builder()
.withDatabase("<database-name>")
.withMinimumNotificationSeverity(NotificationSeverity.OFF)
.build()
);