Skip to main content
You can preface any DQL statement with EXPLAIN to instruct the query engine to return only the execution plan for the statement. This takes the form of a series of operators presented as JSON objects which represent the various component actions the planner has determined as necessary in order to execute the statement.
The statement isn’t executed when using EXPLAIN, only the parsing and planning stages take place.
EXPLAIN Syntax Diagram Example:
DQL
produces:
A query plan is read from top to bottom to follow the flow of data through it. In the above example the first action is to scan the collection with a full collection scan (see Access paths) then project the entire document. In the example below an index scan is used to locate the data which matches “field1 = 1”, is then filtered (the index scan filter is re-applied along with application of the other filter (“field2 = 2”)), is then grouped, projected and the results ordered (the final projection is just a second part to the projection present when necessary in certain plans):
DQL

Join plans

When a SELECT statement uses one or more JOIN terms, each join appears in the plan as an nlJoin (nested-loop join) operator. Read the plan from top to bottom: the outer (driving) collection is scanned or probed first, then for each outer row the nlJoin operator evaluates its inner sub-plan to find matching documents in the joined collection.

The nlJoin operator

DQL
Key things to observe:
  • Dynamic span bounds. The index scan span on the inner collection references the outer alias — "expr": "c.cust_id" — meaning the span is re-evaluated for every outer row. This is how the nested-loop join drives the inner index lookup.
  • fetch step. When the query projects fields not stored in the index, a fetch operator retrieves the full document after the index scan. If all projected fields are covered by the index, the fetch step is absent entirely (see Covering index scans below).

LEFT OUTER JOIN

A LEFT OUTER JOIN produces the same plan shape as INNER JOIN with the addition of "outer": true on the nlJoin operator, which instructs the engine to emit a padded row (inner fields set to MISSING) when the inner collection has no match:

RIGHT OUTER JOIN (rewritten)

A RIGHT OUTER JOIN is silently rewritten so that the originally-right collection becomes the outer (driving) leg. The plan therefore shows the right-hand collection scanned first, with the left-hand collection as the nlJoin inner leg and "outer": true set on the operator:
DQL
In the resulting plan orders (o) is the outer leg and customers (c) is the inner leg, looked up via an index on cust_id.

Multi-collection plans

A three-collection join produces two sequential nlJoin operators. The second operator’s condition may reference aliases introduced by the first join:
Each additional JOIN term appends a further nlJoin to the sequence.

Covering index scans in joins

When the query projects only fields stored in the inner collection’s index (plus _id, which is always available), the planner uses a covering index scan and omits the fetch step entirely, reducing document reads significantly. The index scan descriptor includes "covering": true:
DQL
To maximise the chance of a covering scan, create a composite index that includes both the join key and any additional fields projected or filtered from the inner collection:
DQL

Intersect scans on join inner legs

When the ON condition or WHERE clause provides multiple filterable predicates against the inner collection, the planner may combine several index scans into an intersectScan before the fetch — exactly as for non-join queries. The intersectScan appears inside the nlJoin’s inner sequence:
DQL

ID-based joins

When the ON condition equates an outer field to _id of the inner collection, the planner uses an idScan — a direct document lookup by ID — rather than a secondary index scan. No index on the inner collection is required:
For more on join syntax, index requirements, and query directives, see Joins.