Todos los productos
Search
Centro de documentos

PolarDB:Ejecutar la instrucción EXPLAIN para consultar los planes de ejecución de consultas paralelas elásticas

Última actualización: Sep 05, 2026

Utilice la instrucción EXPLAIN para inspeccionar los planes de ejecución y confirmar si una consulta se ejecuta en paralelo en PolarDB for MySQL.

Funcionamiento

Cuando la consulta paralela elástica está activa, PolarDB divide una consulta en subtareas y las asigna a varios workers en uno o más nodos. El plan de ejecución refleja esto al introducir operadores específicos del procesamiento paralelo junto con los campos estándar de salida de EXPLAIN.

Campos estándar de salida de EXPLAIN:

Campo

Descripción

Id

Identificador del paso en el plan de ejecución

Select_type

Tipo de SELECT (por ejemplo, SIMPLE, PRIMARY, DEPENDENT SUBQUERY)

Table

Tabla a la que accede este paso, o un nodo virtual como <gather2>

Type

Tipo de unión o acceso (por ejemplo, index, range, ALL)

Key

Índice utilizado

Rows

Número estimado de filas examinadas

Filtered

Porcentaje estimado de filas que superan la condición de filtro

Extra

Información adicional de ejecución, incluidos los operadores paralelos

Operadores específicos del procesamiento paralelo:

Operador

Dónde aparece

Función

Gather

Nivel superior o subárbol

Recopila los resultados parciales de todos los workers y los fusiona en un único conjunto de resultados

Parallel scan (N workers)

Campo Extra

Indica un escaneo paralelo de tabla o índice con N workers

Parallel pushdown

Campo Extra

Envía una subconsulta dependiente a los workers para su ejecución en paralelo

Broadcast

Salida de EXPLAIN FORMAT=TREE

Envía una copia de un conjunto de datos a todos los workers

Shared access

Salida de EXPLAIN FORMAT=TREE

Ejecuta una subconsulta paralela una sola vez y comparte el resultado entre los workers

El nodo Gather en el árbol del plan marca el límite entre la ejecución paralela y la serial. Todo lo que se encuentra por debajo de un nodo Gather se ejecuta en paralelo; lo que está por encima se ejecuta de forma serial en el coordinador.

Tabla de ejemplo

Todos los ejemplos de este tema utilizan la tabla pq_test.

Esquema:

SHOW CREATE TABLE pq_test\G
*************************** 1. row ***************************
       Table: pq_test
Create Table: CREATE TABLE `pq_test` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `help_topic_id` INT(10) UNSIGNED NOT NULL,
  `name` CHAR(64) NOT NULL,
  `help_category_id` SMALLINT(5) UNSIGNED NOT NULL,
  `description` TEXT NOT NULL,
  `example` TEXT NOT NULL,
  `url` TEXT NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21495809 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

Tamaño de la tabla:

SHOW TABLE STATUS\G
*************************** 1. row ***************************
           Name: pq_test
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 20064988
 Avg_row_length: 1898
    Data_length: 38085328896
Max_data_length: 0
   Index_length: 0
      Data_free: 4194304
 Auto_increment: 21495809
    Create_time: 2019-07-30 01:35:27
     Update_time: NULL
      Check_time: NULL
       Collation: utf8_general_ci
        Checksum: NULL
 Create_options:
         Comment:
1 row in set (0.02 sec)

Consulta de prueba:

SELECT COUNT(*) FROM pq_test;

Consultar un plan de consulta no paralela

Establezca max_parallel_degree en 0 para deshabilitar la consulta paralela y luego ejecute EXPLAIN:

SET max_parallel_degree=0; EXPLAIN SELECT COUNT(*) FROM pq_test\G
*************************** 1. row ***************************
           Id: 1
  Select_type: SIMPLE
        Table: pq_test
   Partitions: NULL
         Type: index
Possible_keys: NULL
          Key: PRIMARY
      Key_len: 8
          Ref: NULL
         Rows: 20064988
     Filtered: 100.00
        Extra: Using index
1 row in set, 1 warning (0.03 sec)

El plan muestra un escaneo de índice estándar. No existe ningún nodo Gather y el campo Extra no contiene el marcador Parallel scan, lo cual confirma que la consulta se ejecuta de forma serial.

Consultar un plan de consulta paralela

PolarDB for MySQL 8.0.1: EXPLAIN

EXPLAIN SELECT COUNT(*) FROM pq_test\G
*************************** 1. row ***************************
           Id: 1
  Select_type: SIMPLE
        Table: <gather2>
   Partitions: NULL
         Type: ALL
Possible_keys: NULL
          Key: NULL
      Key_len: NULL
          Ref: NULL
         Rows: 20064988
     Filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           Id: 2
  Select_type: SIMPLE
        Table: pq_test
   Partitions: NULL
         Type: index
Possible_keys: NULL
          Key: PRIMARY
      Key_len: 8
          Ref: NULL
         Rows: 10032494
     Filtered: 100.00
        Extra: Parallel scan (2 workers); Using index
2 rows in set, 1 warning (0.00 sec)

Aparecen dos filas en lugar de una:

  • La fila 1 (<gather2>) corresponde a la operación Gather, que recopila y fusiona los resultados de todos los workers. Esta fila marca el límite paralelo: todo lo que está por debajo se ejecuta en paralelo.

  • La fila 2 muestra el escaneo real sobre pq_test con Parallel scan (2 workers) en el campo Extra, lo cual confirma que 2 workers ejecutan el escaneo de índice en paralelo.

PolarDB for MySQL 8.0.2: EXPLAIN FORMAT=TREE

EXPLAIN FORMAT=TREE devuelve un plan en forma de árbol con detalles más completos sobre la estructura de ejecución paralela:

EXPLAIN FORMAT=TREE SELECT COUNT(*) FROM pq_test\G
*************************** 1. row ***************************
EXPLAIN: -> Count rows in gather  (cost=... rows=1)
    -> Gather (slice: 1; workers: 2, nodes: 2)
        -> Parallel index scan on pq_test using PRIMARY, with parallel partitions: 8  (cost=... rows=20064988)

Lectura de arriba hacia abajo:

  • Count rows in gather: el coordinador agrega el conteo final a partir de los resultados de los workers.

  • Gather (slice: 1; workers: 2, nodes: 2): 2 workers se ejecutan en 2 nodos. Este es el límite paralelo.

  • Parallel index scan on pq_test using PRIMARY, with parallel partitions: 8: el índice se divide en 8 particiones que se escanean en paralelo.

Consultar un plan de consulta paralela con subconsultas

PolarDB for MySQL 8.0.1: EXPLAIN

EXPLAIN SELECT
    o_orderpriority,
    COUNT(*) as order_count
FROM
    orders
WHERE
    o_orderdate >= '1994-04-01'
    AND o_orderdate < date_add('1994-04-01', interval '3' month)
    AND exists (
        SELECT
            *
        FROM
            lineitem
        WHERE
            l_orderkey = o_orderkey
            and l_commitdate < l_receiptdate
    )
GROUP BY
    o_orderpriority
ORDER BY
    o_orderpriority\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: <gather1.1>
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1489068
     filtered: 100.00
        Extra: Using temporary; Using filesort
*************************** 2. row ***************************
           id: 1
  select_type: PRIMARY
        table: orders
   partitions: NULL
         type: range
possible_keys: i_o_orderdate
          key: i_o_orderdate
      key_len: 3
          ref: NULL
         rows: 568369
     filtered: 100.00
        Extra: Parallel scan (2 workers); Using index condition; Using where; Using temporary
*************************** 3. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: lineitem
   partitions: NULL
         type: ref
possible_keys: PRIMARY,i_l_orderkey
          key: PRIMARY
      key_len: 4
          ref: tpch_10.orders.O_ORDERKEY
         rows: 4
     filtered: 33.3
        Extra: Parallel pushdown; Using where
3 rows in set, 2 warnings (0.01 sec)

La fila 3 presenta select_type: DEPENDENT SUBQUERY con Extra: Parallel pushdown. Esto significa que la subconsulta se envía a cada worker para ejecutarse en paralelo, en lugar de evaluarse una sola vez en el coordinador.

PolarDB for MySQL 8.0.2: EXPLAIN FORMAT=TREE

Ejemplo 1

EXPLAIN FORMAT=TREE SELECT
    o_orderpriority,
    COUNT(*) as order_count
FROM
    orders
WHERE
    o_orderdate >= '1994-04-01'
    AND o_orderdate < date_add('1994-04-01', interval '3' month)
    AND exists (
        SELECT
            *
        FROM
            lineitem
        WHERE
            l_orderkey = o_orderkey
            and l_commitdate < l_receiptdate
    )
GROUP BY
    o_orderpriority
ORDER BY
    o_orderpriority\G
*************************** 1. row ***************************
EXPLAIN: -> Sort: <temporary>.o_orderpriority
    -> Table scan on <temporary>
        -> Aggregate using temporary table  (cost=1746887.76 rows=1489068)
            -> Gather (slice: 1; workers: 2)  (cost=1597980.96 rows=1489068)
                -> Table scan on <temporary>
                    -> Aggregate using temporary table  (cost=1486290.85 rows=744534)
                        -> Filter: exists(select #2)  (cost=772982.43 rows=568369)
                            -> Parallel index range scan on orders using i_o_orderdate, with index condition: ((orders.O_ORDERDATE >= DATE'1994-04-01') and (orders.O_ORDERDATE < <cache>(('1994-04-01' + interval '3' month)))), with parallel partitions: 89  (cost=772982.43 rows=568369)
                            -> Select #2 (subquery in condition; dependent)
                                -> Limit: 1 row(s)
                                    -> Filter: (lineitem.L_COMMITDATE < lineitem.L_RECEIPTDATE)  (cost=1.14 rows=1)
                                        -> Index lookup on lineitem using PRIMARY (L_ORDERKEY=orders.O_ORDERKEY)  (cost=1.14 rows=4)

1 row in set, 1 warning (0.02 sec)

El nodo Gather (slice: 1; workers: 2) separa el subárbol paralelo (inferior) de la agregación y ordenamiento seriales (superior). La subconsulta (Select #2) está marcada como dependent, lo que significa que se ejecuta dentro de cada worker mediante Parallel pushdown.

Ejemplo 2

EXPLAIN FORMAT=TREE
select
    ps_partkey,
    sum(ps_supplycost * ps_availqty) as value
from
    partsupp,
    supplier,
    nation
where
    ps_suppkey = s_suppkey
    and s_nationkey = n_nationkey
    and n_name = 'IRAN'
group by
    ps_partkey having
        sum(ps_supplycost * ps_availqty) > (
            select
                sum(ps_supplycost * ps_availqty) * 0.0000010000
            from
                partsupp,
                supplier,
                nation
            where
                ps_suppkey = s_suppkey
                and s_nationkey = n_nationkey
                and n_name = 'IRAN'
        )
order by
    value desc limit 1;
| -> Limit: 1 row(s)  (cost=1408498.03 rows=1)
    -> Gather (merge sort; slice: 1; workers: 256; actual workers: 32)  (cost=1408498.03 rows=256)
        -> Limit: 1 row(s)  (cost=1408404.20 rows=1)
            -> Sort: <temporary>.value DESC, limit input to 1 row(s) per chunk  (cost=1408404.20 rows=803182)
                -> Filter: (sum((partsupp.PS_SUPPLYCOST * partsupp.PS_AVAILQTY)) > (select #2))
                    -> Table scan on <temporary>
                        -> Aggregate using temporary table  (cost=1408404.20 rows=803182)
                            -> Inner hash join (partsupp.PS_SUPPKEY = supplier.S_SUPPKEY)  (cost=829770.18 rows=327820)
                                -> Parallel table scan on partsupp, with parallel partitions: 2882, partition_keys: 1  (cost=6347528.15 rows=3176912)
                                -> Hash
                                    -> Broadcast (slice: 2; workers: 256; nodes: 16)  (cost=103382.56 rows=1029632)
                                        -> Nested loop inner join  (cost=409.36 rows=4022)
                                            -> Filter: (nation.N_NAME = 'IRAN')  (cost=2.29 rows=3)
                                                -> Table scan on nation  (cost=2.29 rows=25)
                                            -> Parallel index lookup on supplier using SUPPLIER_FK1 (S_NATIONKEY=nation.N_NATIONKEY), with parallel partitions: 9243  (cost=65.94 rows=1609)
                    -> Select #2 (subquery in condition; run only once; shared access)
                        -> Aggregate: sum(`<collector>`.tmp_field_0)  (cost=825576.85 rows=1)
                            -> Gather (slice: 1; workers: 256; nodes: 16)  (cost=825564.05 rows=256)
                                -> Aggregate: sum((partsupp.PS_SUPPLYCOST * partsupp.PS_AVAILQTY))  (cost=825541.20 rows=1)
                                    -> Inner hash join (partsupp.PS_SUPPKEY = supplier.S_SUPPKEY)  (cost=809150.20 rows=327820)
                                        -> Parallel table scan on partsupp, with parallel partitions: 14405  (cost=6147699.35 rows=3176912)
                                        -> Hash
                                            -> Broadcast (slice: 2; workers: 256; nodes: 16)  (cost=103382.56 rows=1029632)
                                                -> Nested loop inner join  (cost=409.36 rows=4022)
                                                    -> Filter: (nation.N_NAME = 'IRAN')  (cost=2.29 rows=3)
                                                        -> Table scan on nation  (cost=2.29 rows=25)
                                                    -> Parallel index lookup on supplier using SUPPLIER_FK1 (S_NATIONKEY=nation.N_NATIONKEY), with parallel partitions: 9243  (cost=65.94 rows=1609)

Aspectos clave de este plan:

  • Gather (merge sort; slice: 1; workers: 256; actual workers: 32): el modificador merge sort indica que el nodo Gather fusiona resultados previamente ordenados provenientes de los workers, lo que evita volver a ordenarlos por completo en el nivel superior. workers: 256 es el grado de paralelismo configurado; actual workers: 32 es la cantidad que realmente se ejecutó.

  • Select #2 (subquery in condition; run only once; shared access): la subconsulta HAVING se ejecuta una sola vez en paralelo y el resultado se comparte entre todos los workers externos. Esta es la optimización Shared access.

  • Broadcast (slice: 2; workers: 256; nodes: 16): envía el resultado de la unión entre supplier y nation a los 16 nodos. De esta manera, cada nodo puede realizar la unión hash de forma local sin mover datos entre nodos.

  • El bloque de consulta externo no admite la consulta paralela elástica multinodo debido a restricciones de política. Los workers de la consulta externa ejecutan una consulta paralela elástica de nodo único en el nodo donde se entrega la consulta.

Consultar el tiempo de ejecución por worker con EXPLAIN ANALYZE

Disponible en PolarDB for MySQL 8.0.2, EXPLAIN ANALYZE añade los tiempos reales junto con el costo estimado. En las consultas paralelas, registra el tiempo de ejecución de cada worker por operador, incluidos los valores máximo, mínimo y promedio de todos los workers.

EXPLAIN ANALYZE select
    c_name,
    c_custkey,
    o_orderkey,
    o_orderdate,
    o_totalprice,
    sum(l_quantity)
from
    customer,
    orders,
    lineitem
where
    o_orderkey in (
        select
            l_orderkey
        from
            lineitem
        group by
            l_orderkey having
                sum(l_quantity) > 313
    )
    and c_custkey = o_custkey
    and o_orderkey = l_orderkey
group by
    c_name,
    c_custkey,
    o_orderkey,
    o_orderdate,
    o_totalprice
order by
    o_totalprice desc,
    o_orderdate
LIMIT 100;
| -> Limit: 100 row(s)  (cost=14.62 rows=1) (actual time=1.252..1.252 rows=0 loops=1)
    -> Gather (merge sort; slice: 1; workers: 1; nodes: 2)  (cost=14.62 rows=1) (actual time=1.250..1.250 rows=0 loops=1)
        -> Limit: 100 row(s)  (cost=4.52 rows=1) (actual time=0.084,0.084,0.084..0.084,0.084,0.084 rows=0,0,0 loops=1,1,1)
            -> Sort: <temporary>.O_TOTALPRICE DESC, <temporary>.O_ORDERDATE, limit input to 100 row(s) per chunk  (cost=4.52 rows=1) (actual time=0.083,0.083,0.083..0.083,0.083,0.083 rows=0,0,0 loops=1,1,1)
                -> Table scan on <temporary> (actual time=0.070,0.070,0.070..0.070,0.070,0.070 rows=0,0,0 loops=1,1,1)
                    -> Aggregate using temporary table  (cost=4.52 rows=1) (actual time=0.001,0.001,0.001..0.001,0.001,0.001 rows=0,0,0 loops=1,1,1)
                        -> Nested loop inner join  (cost=2.86 rows=4) (actual time=0.039,0.039,0.039..0.039,0.039,0.039 rows=0,0,0 loops=1,1,1)
                            -> Nested loop inner join  (cost=1.45 rows=1) (actual time=0.037,0.037,0.037..0.037,0.037,0.037 rows=0,0,0 loops=1,1,1)
                                -> Parallel table scan on customer, with parallel partitions: 1, partition_keys: 1  (cost=0.35 rows=1) (actual time=0.036,0.036,0.036..0.036,0.036,0.036 rows=0,0,0 loops=1,1,1)
                                -> Filter: <in_optimizer>(orders.O_ORDERKEY,<exists>(select #2))  (cost=1.10 rows=1)
                                    -> Index lookup on orders using ORDERS_FK1 (O_CUSTKEY=customer.C_CUSTKEY)  (cost=1.10 rows=1)
                                    -> Select #2 (subquery in condition; dependent)
                                        -> Limit: 1 row(s)
                                            -> Filter: ((sum(lineitem.L_QUANTITY) > 313) and (<cache>(orders.O_ORDERKEY) = <ref_null_helper>(lineitem.L_ORDERKEY)))
                                                -> Group aggregate: sum(lineitem.L_QUANTITY)
                                                    -> Index scan on lineitem using PRIMARY  (cost=41554048.20 rows=380071042)
                            -> Index lookup on lineitem using PRIMARY (L_ORDERKEY=orders.O_ORDERKEY)  (cost=1.41 rows=4)

El formato de actual time varía según si un operador se ejecuta por encima o por debajo del nodo Gather:

Ubicación del operador

Formato

Ejemplo

Por encima de Gather (coordinador, serial)

actual time=start..end rows=N loops=N

actual time=1.250..1.250 rows=0 loops=1

Por debajo de Gather (workers, paralelo)

actual time=v1,v2,v3..v1,v2,v3 rows=N1,N2,N3 loops=N1,N2,N3

actual time=0.084,0.084,0.084..0.084,0.084,0.084 rows=0,0,0 loops=1,1,1

Para los operadores dentro del subárbol paralelo, cada valor separado por comas corresponde a un worker. Esto le permite identificar qué operador representa el cuello de botella entre los workers y si el tiempo de ejecución se distribuye de manera uniforme.