Source: https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/data-admin/tuning/explain/ Documentation version: v5.3.2 # EXPLAIN command The `EXPLAIN` command displays the execution plan of a query. Unlike actually executing a query, the `EXPLAIN` command outlines the steps taken during query execution, which is useful for debugging and optimizing queries. When using the ngql CLI, you can use the `:planstyle` command to switch between execution plan display styles (nest/table/box). The default style is nest. For more information, see [Execution plan style setting](https://dev-docs.nebula-graph.io/v5.3.2/utilities/ngql/usage/#execution_plan_style_setting) in the ngql documentation. Optimization Pipeline The `EXPLAIN` command allows you to view the execution plan at different optimization stages. The full optimization pipeline is as follows: - `RAW`: The original logical plan before any optimization. - `DECORRELATED`: The logical plan after subquery decorrelation. - `CBO`: The logical plan after the CBO (Cost-Based Optimizer) stage. - Default (no stage specified): The final execution plan after all optimization stages are complete. By examining plans at different stages, you can pinpoint specific issues in the optimization process, such as whether subquery rewriting took effect or whether the join order is reasonable. ## Syntax diagramsource explain_command ```` explain_command ::= 'EXPLAIN' ( 'RAW' | 'CBO' | 'DECORRELATED' )? ( 'FORMAT' '=' '"' ( 'VERBOSE' | 'DEFAULT' ) '"' )? gql_query ```` ## Parameters ### `RAW | CBO | DECORRELATED` Specifies the optimization stage at which to view the execution plan. Each stage corresponds to a different processing node in the query optimization pipeline. The stages are described below: | Stage | Description | `RAW` | Returns the original logical plan before any optimization. | `DECORRELATED` | Returns the logical plan after subquery decorrelation. In this stage, correlated subqueries — those that reference outer variables and would otherwise require re-execution of the inner query for each row — are rewritten into equivalent non-nested forms (such as joins or aggregations), enabling subsequent standard optimizations. | `CBO` | Returns the logical plan after the CBO (Cost-Based Optimizer) stage. This stage focuses on join reordering and cost-based decisions for graph pattern matching. Not all queries go through this stage; it takes effect only for queries involving eligible graph patterns or join reordering scenarios. The optimizer's ability to change the expansion order of `MATCH` patterns is controlled separately by `enable_reorder`, which defaults to `false`. If this parameter is omitted, the final execution plan after all optimization stages is returned. ### `FORMAT = "VERBOSE" | FORMAT = "DEFAULT"` There are two output formats for the execution plan: `VERBOSE` and `DEFAULT`. - The `VERBOSE` FORMAT is more detailed and is suitable for debugging and optimizing queries. Use the `VERBOSE` FORMAT by specifying the `FORMAT = VERBOSE` option. - The `DEFAULT` FORMAT is a simplified version of the `VERBOSE` FORMAT. The `DEFAULT` FORMAT is used when both `FORMAT = DEFAULT` and `FORMAT = "VERBOSE"` options are omitted. The information returned by both formats is as follows: | Parameter | Description | `plan` | Represents the operators in the execution plan. Operators are listed in bottom-to-top execution order. | `details` | Provides detailed information about the operator, including the data of nodes and edges to be accessed. | `columns` | Represents the columns processed by the operator during execution. This column is displayed only when `FORMAT = VERBOSE` is specified. ### `gql_query` The GQL query whose execution plan is to be displayed. ## Examples The following examples are based on the [example dataset](https://dev-docs.nebula-graph.io/v5.3.2/database-gql-reference/overview/example-dataset/). ### Specify `DEFAULT` to view the optimized execution plan The following example explicitly specifies `FORMAT = "DEFAULT"`, which is equivalent to omitting `FORMAT = "DEFAULT"` since `DEFAULT` is the DEFAULT FORMAT. It returns the optimized execution plan. #### View nested execution plan ```` EXPLAIN FORMAT = "DEFAULT" USE movie MATCH (a:Person)-[e]->(b@Movie) LIMIT 1 RETURN b ```` Return: ```` • Limit │ details: Offset:0, Limit:1, Partial:false, Approx │ :false │ └── • HashJoin(Inner)(DF:/_id) │ details: Project: b, element_id(b)=_right_node_i │ d(e) │ ├── • [S]NodesScan │ details: TableScan: {Movie: {name, id}}, b{} │ └── • [S]Filter │ details: in(node_type_id(_left_node_id(e)), [1024 │ ,1025,1026]) │ └── • [S]EdgesScan details: direction: outgoing: true, incoming: fal se, undirected0: false, undirected1: fal se, TableScan: {Watch: {}}, TableScan: { Direct: {}}, TableScan: {Act: {}}, e{} Execution Plan: [Px] means pipeline-x and [S] means storage side. Elapsed Time: parse time : 624 us build time : 951 us optimize time : 929 us serialize time: 41 us total time : 3070 us ```` Each level of the nested view corresponds to an operator, and the indentation between levels indicates the dependency between operators. For example, the `Limit` operator depends on `HashJoin`. #### View table execution plan When using the ngql CLI, you can use the `:planstyle` command to display the execution plan in the table style. For more information, see [Execution plan style setting](https://dev-docs.nebula-graph.io/v5.3.2/utilities/ngql/usage/#execution_plan_style_setting) in the ngql documentation. ```` :planstyle table EXPLAIN FORMAT = "DEFAULT" USE movie MATCH (a:Person)-[e]->(b@Movie) LIMIT 1 RETURN b ```` Return: ```` +----------------------------+------------------------------------------------------------------------------------------------------+ | plan | details | +----------------------------+------------------------------------------------------------------------------------------------------+ | Limit | Offset:0, Limit:1, Partial:false, Approx:false | | └─HashJoin(Inner)(DF:/_id) | Project: b, element_id(b)=_right_node_id(e) | | ├─[S]NodesScan | TableScan: {Movie: {name, id}}, b{} | | └─[S]Filter | in(node_type_id(_left_node_id(e)), [1024,1025,1026]) | | └─[S]EdgesScan | direction: outgoing: true, incoming: false, undirected0: false, undirected1: false, TableScan: {W... | +----------------------------+------------------------------------------------------------------------------------------------------+ Execution Plan: [Px] means pipeline-x and [S] means storage side. Elapsed Time: parse time : 269 us build time : 314 us optimize time : 350 us serialize time: 61 us total time : 1272 us ```` Each row in the table corresponds to an operator. The indentation in the `plan` column indicates the dependency between operators. For example, the `Limit` operator depends on `HashJoin`. #### View box execution plan When using the ngql CLI, you can use the `:planstyle` command to display the execution plan in the box style. For more information, see [Execution plan style setting](https://dev-docs.nebula-graph.io/v5.3.2/utilities/ngql/usage/#execution_plan_style_setting) in the ngql documentation. ```` :planstyle box EXPLAIN FORMAT = "DEFAULT" USE movie MATCH (a:Person)-[e]->(b@Movie) LIMIT 1 RETURN b ```` Return: ```` ┌──────────────────────────────────────┐ │ Limit │ │ -------------------------------- │ │ Offset:0, Limit:1, Partial:false │ │ , Approx:false │ └──────────────────┬───────────────────┘ ┌──────────────────┴───────────────────┐ │ HashJoin(Inner)(DF:/_id) │ │ -------------------------------- │ │ Project: b, element_id(b)=_righ │────────────────────┐ │ t_node_id(e) │ │ └──────────────────┬───────────────────┘ │ ┌──────────────────┴───────────────────┐ ┌──────────────────┴───────────────────┐ │ [S]NodesScan │ │ [S]Filter │ │ -------------------------------- │ │ -------------------------------- │ │ TableScan: {Movie: {name, id}}, │ │ in(node_type_id(_left_node_id(e) │ │ b{} │ │ ), [1024,1025,1026]) │ └──────────────────────────────────────┘ └──────────────────┬───────────────────┘ ┌──────────────────┴───────────────────┐ │ [S]EdgesScan │ │ -------------------------------- │ │ direction: outgoing: true, incom │ │ ing: false, undirected0: false, │ │ undirected1: false, TableScan: { │ │ Direct: {}}, TableScan: {Watch: │ │ {}}, TableScan: {Act: {}}, e{} │ └──────────────────────────────────────┘ Execution Plan: [Px] means pipeline-x and [S] means storage side. Elapsed Time: parse time : 820 us build time : 1154 us optimize time : 1116 us serialize time: 92 us total time : 3963 us ```` Each box represents an operator, and the connections between boxes indicate that the upper operator depends on the output of the lower operator. For example, the `Limit` operator depends on `HashJoin`. ### Specify `RAW` to view the unoptimized logical plan ```` EXPLAIN RAW USE movie MATCH (a:Person)-[e]->(b@Movie) LIMIT 1 RETURN b ```` The above example specifies the `RAW` option and returns the unoptimized logical plan for the query `MATCH (a:Person)-[e]->(b@Movie) LIMIT 1 RETURN b`. Return: ```` • Project │ details: b │ └── • Limit │ details: Offset:0, Limit:1, Partial:false, Approx │ :false │ └── • LogicalJoin(Cross) │ ├── • Values(UNIT) │ details: UNIT │ └── • Project │ details: b, e, a │ └── • LogicalJoin(Inner) │ details: element_id(b)=_right_node_id(e) │ ├── • GetNodes │ details: Movie,, b{_GraphId,_id,id,name} │ └── • LogicalJoin(Inner) │ details: _left_node_id(e)=element_id(a) │ ├── • [S]GetEdges │ details: Act,Direct,Watch,, e{rate} │ └── • GetNodes details: Actor,Director,User,, a{_GraphId,_id,bir thDate,id,name} Execution Plan: [Px] means pipeline-x and [S] means storage side. Elapsed Time: parse time : 173 us build time : 0 us optimize time : 0 us serialize time: 25 us total time : 582 us ```` Each level of the nested view corresponds to an operator, and the indentation between levels indicates the dependency between operators. ### Specify `DECORRELATED` to view the logical plan after decorrelation `EXPLAIN DECORRELATED` returns the execution plan after subquery decorrelation, showing the logical plan after correlated subqueries have been rewritten into equivalent non-nested forms. ```` ''' EXPLAIN DECORRELATED USE movie MATCH (a@Actor) WHERE EXISTS {USE movie MATCH (v@Actor) RETURN v} RETURN a.name LIMIT 5 ''' ```` Return: ```` • Project │ details: a.name │ └── • Limit │ details: Offset:0, Limit:5, Partial:false, Approx │ :false │ └── • LogicalJoin(Cross) │ ├── • GetNodes │ details: Actor,, a{_GraphId,_id,birthDate,id,name │ } │ └── • Project │ details: CASE WHEN @anon2 = 1 THEN true ELSE fals │ e END AS @anon2 │ └── • Filter │ details: CASE WHEN @anon2 = 1 THEN true ELSE fals │ e END │ └── • Aggregate │ details: Partial: false, Single: true, Memory: tr │ ue, AGGREGATE count(true) AS @anon2 │ └── • Project │ details: v │ └── • Limit │ details: Offset:0, Limit:1, Partial:false, Approx │ :false │ └── • LogicalJoin(Cross) │ ├── • Values(UNIT) │ details: UNIT │ └── • GetNodes details: Actor,, v{_GraphId,_id,birthDate,id,name } Execution Plan: [Px] means pipeline-x and [S] means storage side. Elapsed Time: parse time : 497 us build time : 572 us optimize time : 614 us serialize time: 55 us total time : 2147 us ```` Note The `DECORRELATED` stage has a significant effect primarily on queries that contain correlated subqueries. For queries without subqueries, the output of the `DECORRELATED` stage may be identical or very similar to that of the `RAW` stage. ### Specify `CBO` to view the logical plan after CBO reordering `EXPLAIN CBO` returns the execution plan after the Cost-Based Optimizer (CBO) stage, showing the logical plan after that stage finishes and before final plan generation. If you want to observe how the optimizer changes the expansion order of `MATCH` patterns, enable `enable_reorder` explicitly. ```` ''' EXPLAIN CBO /*+ SET_VAR(enable_reorder=true) */ USE movie MATCH (a:Person)-[e]->(b@Movie) LIMIT 1 RETURN b ''' ```` Return: ```` • Limit │ details: Offset:0, Limit:1, Partial:false, Approx │ :false │ └── • Project │ details: b │ └── • Extend │ details: Emit: from=true, edge=true, to=true, Dir │ ection: outgoing: false, incoming: true, │ undirected0: false, undirected1: false, │ ToNode: a, EdgeField: e, FromNode: b, M │ ode: All, Graph: movie(1024) │ └── • GetNodes details: Movie,, b{_GraphId,_id,id,name} Execution Plan: [Px] means pipeline-x and [S] means storage side. Elapsed Time: parse time : 860 us build time : 812 us optimize time : 708 us serialize time: 38 us total time : 2987 us ```` Note Not all queries go through the CBO stage. `EXPLAIN CBO` produces output that differs from the DEFAULT plan only for queries involving eligible graph patterns or join reordering scenarios. For queries that do not involve CBO optimization, the output may be the same as the `DECORRELATED` stage. `enable_reorder` defaults to `false`. If you want to compare a reordered `MATCH` plan with the default textual-order plan, enable it explicitly at the query, session, or global level. If the plan is unexpected or you want to understand why CBO chose a specific plan, enable `enable_cbo_trace=true` and inspect the `nebula-graphd.INFO` log. `enable_cbo_trace` is a session-level configuration option and its default value is `false`. Example: ```` ''' EXPLAIN /*+ set_var(enable_cbo_trace=true) */ USE movie MATCH (a:Person)-[e]->(b@Movie) LIMIT 1 RETURN b ''' ```` If the CBO optimizer chose a specific plan, you can find the following information in the `nebula-graphd.INFO` log: ```` I20260326 14:45:11.947098 1439136 CboTrace.cpp:31] [CBO] ===== Trace Summary ===== Pattern: nodes=2, edges=1, predicates=0 Leaf Plans ... [CBO] ===== End Trace Summary ===== ```` ### Specify `FORMAT = "VERBOSE"` to view the detailed optimized execution plan ```` EXPLAIN FORMAT = "VERBOSE" USE movie MATCH (a:Person)-[e]->(b@Movie) LIMIT 1 RETURN b ```` The above example uses the `VERBOSE` FORMAT by specifying `FORMAT = "VERBOSE"` to display the optimized execution plan for the query `USE movie MATCH (a:Person)-[e]->(b@Movie) LIMIT 1 RETURN b`. Return: ```` • Limit │ details: Offset:0, Limit:1, Partial:false, Approx │ :false │ columns: [b] │ └── • HashJoin(Inner)(DF:/_id) │ details: Project: b:NodeRef<(Movie)>, element_id │ (b::NodeRef<(Movie)>)::INT64=(_right_nod │ e_id(e::EdgeRef<(Actor)-[Act]->(Movie), │ (Director)-[Direct]->(Movie), (User)-[Wa │ tch]->(Movie)>))::INT64 │ columns: [b] │ ├── • [S]NodesScan │ details: TableScan: {Movie: {name, id}}, b{} │ columns: [b] │ └── • [S]Filter │ details: (in((node_type_id((_left_node_id(e::Edge │ Ref<(Actor)-[Act]->(Movie), (Director)-[ │ Direct]->(Movie), (User)-[Watch]->(Movie │ )>))::INT64))::INT16, [1024,1025,1026]:: │ LIST))::BOOL │ columns: [e] │ └── • [S]EdgesScan details: direction: outgoing: true, incoming: fal se, undirected0: false, undirected1: fal se, TableScan: {Watch: {}}, TableScan: { Direct: {}}, TableScan: {Act: {}}, e{} columns: [e] Execution Plan: [Px] means pipeline-x and [S] means storage side. Elapsed Time: parse time : 234 us build time : 258 us optimize time : 387 us serialize time: 39 us total time : 1184 us ```` Each level of the nested view corresponds to an operator, and the indentation between levels indicates the dependency between operators. For example, the `Limit` operator depends on `HashJoin`. ### Specify `RAW` and `FORMAT = "VERBOSE"` to view the detailed unoptimized logical plan ```` EXPLAIN RAW FORMAT = "VERBOSE" USE movie MATCH (a:Person)-[e]->(b@Movie) LIMIT 1 RETURN b ```` Return: ```` • Project │ details: b::NodeRef<(Movie)> │ columns: [b] │ └── • Limit │ details: Offset:0, Limit:1, Partial:false, Approx │ :false │ columns: [b, e, a] │ └── • LogicalJoin(Cross) │ columns: [b, e, a] │ ├── • Values(UNIT) │ details: UNIT │ columns: [] │ └── • Project │ details: b::NodeRef<(Movie)>, e::EdgeRef<(Actor)- │ [Act]->(Movie), (Director)-[Direct]->(Mo │ vie), (User)-[Watch]->(Movie)>, a::NodeR │ ef<(Actor), (Director), (User)> │ columns: [b, e, a] │ └── • LogicalJoin(Inner) │ details: element_id(b::NodeRef<(Movie)>)::INT64=( │ _right_node_id(e::EdgeRef<(Actor)-[Act]- │ >(Movie), (Director)-[Direct]->(Movie), │ (User)-[Watch]->(Movie)>))::INT64 │ columns: [b, e, a] │ ├── • GetNodes │ details: Movie,, b{_GraphId,_id,id,name} │ columns: [b] │ └── • LogicalJoin(Inner) │ details: (_left_node_id(e::EdgeRef<(Actor)-[Act]- │ >(Movie), (Director)-[Direct]->(Movie), │ (User)-[Watch]->(Movie)>))::INT64=(eleme │ nt_id(a::NodeRef<(Actor), (Director), (U │ ser)>))::INT64 │ columns: [e, a] │ ├── • [S]GetEdges │ details: Act,Direct,Watch,, e{rate} │ columns: [e] │ └── • GetNodes details: Actor,Director,User,, a{_GraphId,_id,bir thDate,id,name} columns: [a] Execution Plan: [Px] means pipeline-x and [S] means storage side. Elapsed Time: parse time : 281 us build time : 0 us optimize time : 0 us serialize time: 39 us total time : 775 us ```` Each level of the nested view corresponds to an operator, and the indentation between levels indicates the dependency between operators.