Use EXPLAIN to view DN execution plans

更新时间:
复制 MD 格式

PolarDB-X distributes a SQL query across multiple data nodes (DNs) for parallel execution. When DNs generate inconsistent execution plans—due to differences in data distribution or statistics—query performance degrades and is hard to diagnose. The EXPLAIN ... EXECUTE command family lets you inspect the actual execution plan on each DN, so you can pinpoint the problematic shard and understand why it diverged.

How it works

PolarDB-X has two node types: compute nodes (CNs) and data nodes (DNs). When a query arrives, the CN parses it, generates a logical execution plan, and dispatches physical SQL to the DNs. Each DN then builds its own execution plan—the DN execution plan—based on local statistics and data distribution.

Because DN execution plans are generated independently, they can differ across shards even for the same query. These differences are the most common cause of slow queries in PolarDB-X.

Choose a command

Start with EXPLAIN EXECUTE to get an overview, then drill down as needed.

CommandWhen to use
EXPLAIN EXECUTEFirst step. Aggregates execution plans from all DNs and surfaces plan inconsistencies and data skew in the Extra column.
EXPLAIN DIFF_EXECUTEWhen EXPLAIN EXECUTE shows Different plan. Lists only the DNs whose plans differ, so you can compare them side by side.
EXPLAIN ALL_EXECUTEWhen you need to audit every DN's plan in detail. Output can be large.
EXPLAIN TREE_EXECUTEDeep analysis of a single DN's plan in a readable tree format. Compatible with MySQL 8.0.
EXPLAIN JSON_EXECUTEDeep analysis of a single DN's plan in JSON format, with full cost breakdown from the optimizer. Compatible with MySQL 8.0.
EXPLAIN ANALYZE_EXECUTEExecutes the query and reports actual runtime statistics alongside estimated costs. Use on small tables or in non-production environments. Compatible with MySQL 8.0.

Prerequisites

Before you begin, ensure that you have:

  • An instance running version polardb-2.5.0_5.4.20-20250618_xcluster8.4.20-20250612 or later

Note

For version naming rules, see Release notes. To check or update your instance version, see View and update the version of an instance.

Run a quick diagnosis with EXPLAIN EXECUTE

EXPLAIN EXECUTE is the starting point. It sends all physical SQL statements to their corresponding physical tables on each DN, collects the resulting execution plans, and compares them. The output format mirrors the native MySQL EXPLAIN output, with two additional fields in the Extra column:

FieldDescription
Different plan(phyTb_1, phyTb_2, ...)The listed physical table shards use a different execution plan from all other shards. A common cause is that the optimizer chose a different index for those shards.
Scan rows(min, max)The range of rows the DN optimizer estimates it will scan across all physical table shards. A large gap between min and max suggests data skew.
Note

In versions before polardb-2.5.0_5.4.20-20250618_xcluster8.4.20-20250612, EXPLAIN EXECUTE randomly selected a single physical SQL statement to display. The current version compares plans from all physical SQL statements.

Example:

mysql> EXPLAIN EXECUTE SELECT col1 FROM exe_tb WHERE col1 < 6\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: exe_tb
   partitions: NULL
         type: index
possible_keys: auto_shard_key_col1
          key: auto_shard_key_col1
      key_len: 5
          ref: NULL
         rows: 2000
     filtered: 100
        Extra: Different plan(exe_tb_58Db_00001,exe_tb_58Db_00015); Scan rows(1, 66819); Using where; Using index

Reading the Extra column:

  • Different plan(exe_tb_58Db_00001,exe_tb_58Db_00015) — These two shards use different execution plans. All other shards share the same plan.

  • Scan rows(1, 66819) — Estimated scan row counts range from 1 to 66,819 across shards. This gap indicates probable data skew.

Next step: Use EXPLAIN DIFF_EXECUTE to see exactly how the plans differ.

Locate plan differences with EXPLAIN DIFF_EXECUTE

EXPLAIN DIFF_EXECUTE lists only the physical tables whose execution plans differ from the majority. The table column shows the physical table name for each row.

mysql> EXPLAIN DIFF_EXECUTE SELECT col1 FROM exe_tb WHERE col1 < 6\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: exe_tb_58Db_00001
         type: index
possible_keys: auto_shard_key_col1
          key: auto_shard_key_col1
      key_len: 5
          ref: NULL
         rows: 2000
     filtered: 100
        Extra: Different plan(exe_tb_58Db_00001,exe_tb_58Db_00015); Scan rows(1, 44939); Using where; Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: exe_tb_58Db_00015
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1009
     filtered: 33.32999801635742
        Extra: Using where

Comparing the two rows:

  • exe_tb_58Db_00001 uses type: index (index scan) — efficient.

  • exe_tb_58Db_00015 uses type: ALL (full table scan) with no index selected — inefficient.

The shard exe_tb_58Db_00015 is the problematic one. Its statistics likely differ from the other shards, causing the optimizer to skip the index.

View all plans with EXPLAIN ALL_EXECUTE

EXPLAIN ALL_EXECUTE lists the execution plan for every physical table. Use this when you need a complete audit rather than just the outliers.

mysql> EXPLAIN ALL_EXECUTE SELECT col1 FROM exe_tb WHERE col1 < 6\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: exe_tb_58Db_00001
   partitions: NULL
         type: index
possible_keys: auto_shard_key_col1
          key: auto_shard_key_col1
      key_len: 5
          ref: NULL
         rows: 2000
     filtered: 100
        Extra: Different plan(exe_tb_58Db_00001,exe_tb_58Db_00015); Scan rows(1, 44939); Using where; Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: exe_tb_58Db_00004
   partitions: NULL
         type: range
possible_keys: auto_shard_key_col1
          key: auto_shard_key_col1
      key_len: 5
          ref: NULL
         rows: 44939
     filtered: 100
        Extra: Using where; Using index
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: exe_tb_58Db_00005
   partitions: NULL
         type: index
possible_keys: auto_shard_key_col1
          key: auto_shard_key_col1
      key_len: 5
          ref: NULL
         rows: 1
     filtered: 100
        Extra: Using where; Using index
************** Execution plans for other physical tables are omitted here... ********************

Advanced analysis

For cost analysis and deep performance investigation, PolarDB-X supports three MySQL 8.0-compatible output formats. All three display a TABLE column with the physical table name and a PHYSICAL_PLAN column with the plan details.

Note

By default, EXPLAIN TREE_EXECUTE, EXPLAIN JSON_EXECUTE, and EXPLAIN ANALYZE_EXECUTE randomly select one DN and display its plan. To see the plans for all DNs, add the hint /*+TDDL:EXPLAIN_EXECUTE_PHYTB_LEVEL=2*/ before your SQL statement.

EXPLAIN TREE_EXECUTE

Displays the execution plan as a tree with hierarchical indentation. Execution flows from the innermost node (deepest indent) outward.

mysql> EXPLAIN TREE_EXECUTE SELECT col1 FROM exe_tb WHERE col1 < 6;
+------------------------+------------------------------------------------------------+
| TABLE                  | PHYSICAL_PLAN                                              |
+------------------------+------------------------------------------------------------+
| [[exe_tb_58Db_00001]]
 | -> Filter: (exe_tb.col1 < 6)  (cost=202.75 rows=2000)
    -> Index scan on exe_tb using auto_shard_key_col1  (cost=202.75 rows=2000)
 |
+------------------------+------------------------------------------------------------+

Reading the tree bottom-up:

  1. -> Index scan on exe_tb using auto_shard_key_col1 — scans rows using the col1 index.

  2. -> Filter: (exe_tb.col1 < 6) — filters the index scan results.

Each node shows the optimizer's estimated cost and row count, giving you an intuitive picture of where time and resources go.

EXPLAIN JSON_EXECUTE

Outputs the DN optimizer's full analysis in JSON format. Use this for the most detailed cost breakdown.

mysql> EXPLAIN JSON_EXECUTE SELECT col1 FROM exe_tb WHERE col1 < 6;
+------------------------+------------------------------------------+
| TABLE                  | PHYSICAL_PLAN                            |
+------------------------+------------------------------------------+
| [[exe_tb_58Db_00001]]
 | {
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "202.75"
    },
    "table": {
      "table_name": "exe_tb",
      "access_type": "index",
      "possible_keys": [
        "auto_shard_key_col1"
      ],
      "key": "auto_shard_key_col1",
      "used_key_parts": [
        "col1"
      ],
      "key_length": "5",
      "rows_examined_per_scan": 2000,
      "rows_produced_per_join": 2000,
      "filtered": "100.00",
      "using_index": true,
      "cost_info": {
        "read_cost": "2.75",
        "eval_cost": "200.00",
        "prefix_cost": "202.75",
        "data_read_per_join": "31K"
      },
      "used_columns": [
        "col1"
      ],
      "attached_condition": "(`db0_p00000`.`exe_tb`.`col1` < 6)"
    }
  }
} |
+------------------------+------------------------------------------+

Key fields in the JSON output:

FieldDescription
cost_info.query_costTotal estimated cost for the query
cost_info.read_costCost to read rows from storage
cost_info.eval_costCost to evaluate rows against the condition
access_typeHow the table is accessed (for example, index, ALL, range)
keyThe index the optimizer selected
rows_examined_per_scanEstimated rows scanned per pass

EXPLAIN ANALYZE_EXECUTE

Executes the query and returns actual runtime statistics alongside optimizer estimates. Use this to identify which operators are the real bottlenecks.

Important

EXPLAIN ANALYZE_EXECUTE executes the SQL statement. On large tables in a production environment, it can consume significant resources and cause slow queries. Use it on small tables or in a test environment.

mysql> EXPLAIN ANALYZE_EXECUTE SELECT col1 FROM exe_tb WHERE col1 < 6;
+------------------------+-----------------------------------------------------------------------------------------------------------+
| TABLE                  | PHYSICAL_PLAN                                                                                             |
+------------------------+-----------------------------------------------------------------------------------------------------------+
| [[exe_tb_58Db_00001]]
 | -> Filter: (exe_tb.col1 < 6)  (cost=202.75 rows=2000) (actual time=0.028..0.655 rows=2000 loops=1)
    -> Covering index scan on exe_tb using auto_shard_key_col1  (cost=202.75 rows=2000) (actual time=0.026..0.468 rows=2000 loops=1)
 |
+------------------------+-----------------------------------------------------------------------------------------------------------+

Each node shows both estimated and actual values. The actual time field uses the format startup_time..total_time, and rows is the actual number of rows the operator returned.

FieldFormatDescription
actual timestartup_time..total_timeTime to return the first row and total execution time
rowsintegerActual number of rows the operator returned
loopsintegerNumber of times the operator was executed

For example, actual time=0.026..0.468 rows=2000 loops=1 means the index scan took 0.026 to return the first row and 0.468 total, returning 2,000 rows in a single pass.

FAQ

Which command should I start with?

Start with EXPLAIN EXECUTE. If Different plan appears in the Extra column, run EXPLAIN DIFF_EXECUTE to isolate the diverging shards. For cost analysis or actual execution timing on a specific shard, use EXPLAIN TREE_EXECUTE or EXPLAIN ANALYZE_EXECUTE. To audit all shards at once, use EXPLAIN ALL_EXECUTE.