Tipos de EXPLAIN
AST— Árbol de sintaxis abstracta.SYNTAX— Texto de la consulta tras las optimizaciones a nivel de AST.QUERY TREE— Árbol de consulta tras las optimizaciones a nivel de Query Tree.PLAN— Plan de ejecución de la consulta.PIPELINE— Canalización de ejecución de la consulta.ANALYZE— Ejecuta la consulta y anota el plan de ejecución con métricas de runtime medidas.ESTIMATE— Número estimado de filas, marcas y partes que se leerán de las tablas al procesar la consulta.TABLE OVERRIDE— Resultado validado de una sobrescritura de tabla sobre el esquema de una función de tabla.
EXPLAIN AST
Muestra el AST de la consulta. Admite todo tipo de consultas, no soloSELECT.
Configuración:
graph– Imprime el AST como un grafo descrito en el lenguaje de descripción de grafos DOT. Valor predeterminado: 0.
EXPLAIN SYNTAX
Muestra el árbol de sintaxis abstracta (AST) de una consulta tras el análisis sintáctico. Esto se hace analizando la consulta, construyendo el AST y el árbol de consulta, ejecutando opcionalmente el analizador de consultas y las pasadas de optimización, y convirtiendo después el árbol de consulta de nuevo en el AST de la consulta. Configuración:oneline– Imprime la consulta en una sola línea. Valor predeterminado:0.run_query_tree_passes– Ejecuta las pasadas del árbol de consulta antes de volcar el árbol de consulta. Valor predeterminado:0.query_tree_passes– Si se establecerun_query_tree_passes, especifica cuántas pasadas se ejecutan. Si no se especificaquery_tree_passes, se ejecutan todas las pasadas.single_record– Devuelve la consulta reformateada como un único registro multilínea en lugar de un registro por línea. Valor predeterminado:1(controlado por la configuraciónexplain_syntax_single_record). Establézcalo en0para restaurar la salida histórica de un registro por línea, o establezcaexplain_syntax_single_record = 0(globalmente o enSETTINGSpor consulta), o establezcacompatibilityen cualquier versión anterior a26.8.
Query
Response
run_query_tree_passes:
Query
Response
EXPLAIN QUERY TREE
Configuración:run_passes— Ejecuta todas las pasadas del árbol de consulta antes de volcarlo. Valor predeterminado:1.dump_passes— Vuelca información sobre las pasadas utilizadas antes de volcar el árbol de consulta. Valor predeterminado:0.passes— Especifica cuántas pasadas ejecutar. Si se establece en-1, ejecuta todas las pasadas. Valor predeterminado:-1.dump_tree— Muestra el árbol de consulta. Valor predeterminado:1.dump_ast— Muestra el AST de la consulta generado a partir del árbol de consulta. Valor predeterminado:0.
EXPLAIN PLAN
Muestra los pasos del plan de consulta. Configuración:optimize— Controla si las optimizaciones del plan de consulta se aplican antes de mostrar el plan. Predeterminado: 1.header— Imprime el encabezado de salida del paso. Predeterminado: 0.description— Imprime la descripción del paso. Predeterminado: 1.indexes— Muestra los índices utilizados, el número de partes filtradas y el número de gránulos filtrados para cada índice aplicado. Predeterminado: 0. Compatible con tablas MergeTree. A partir de ClickHouse >= v25.9, esta sentencia solo muestra una salida adecuada cuando se usa conSETTINGS use_query_condition_cache = 0, use_skip_indexes_on_data_read = 0.projections— Muestra todas las proyecciones analizadas y su efecto en el filtrado a nivel de parte según las condiciones de la clave primaria de la proyección. Para cada proyección, esta sección incluye estadísticas como el número de partes, filas, marcas y rangos evaluados con la clave primaria de la proyección. También muestra cuántas partes de datos se omitieron debido a este filtrado, sin leer de la propia proyección. Si una proyección se usó realmente para la lectura o solo se analizó para el filtrado puede determinarse mediante el campodescription. Predeterminado: 0. Compatible con tablas MergeTree.actions— Imprime información detallada sobre las acciones del paso. Predeterminado: 1.sorting— Imprime la descripción de ordenación de cada paso del plan que produce una salida ordenada. Predeterminado: 0.keep_logical_steps— Conserva los pasos lógicos del plan para joins en lugar de convertirlos en implementaciones físicas de join. Predeterminado: 0.json— Imprime los pasos del plan de consulta como una fila en formato JSON. Predeterminado: 0. Se recomienda usar el formato TabSeparatedRaw (TSVRaw) para evitar escapes innecesarios.input_headers— Imprime los encabezados de entrada del paso. Predeterminado: 0. Principalmente útil solo para desarrolladores al depurar problemas relacionados con discrepancias entre los encabezados de entrada y salida.column_structure— Imprime también la estructura de las columnas en los encabezados, además de su nombre y tipo. Predeterminado: 0. Principalmente útil solo para desarrolladores al depurar problemas relacionados con discrepancias entre los encabezados de entrada y salida.distributed— Muestra los planes de consulta ejecutados en nodos remotos para tablas distribuidas o réplicas paralelas. No compatible conjson. Predeterminado: 0.compact— Cuando está habilitado, oculta del plan los pasos de expresión y la información detallada de las acciones (entradas, funciones, alias y posiciones de salida). Solo tiene efecto cuandoactions = 1. Predeterminado: 1.pretty— Imprime el árbol del plan usando caracteres de dibujo de líneas (├──, └──, │) en lugar de sangría para visualizar la jerarquía. También muestra en línea las propiedades del paso de join. Predeterminado: 1.
De forma predeterminada,
explain_query_plan_default = 'pretty', por lo que actions, compact y pretty se inicializan en 1 y el plan se representa en la forma compacta, visualmente estructurada y anotada con acciones. Especificar cualquiera de estas opciones explícitamente en la sentencia EXPLAIN (por ejemplo, EXPLAIN actions = 0, compact = 0, pretty = 0 SELECT ...) siempre sobrescribe el valor predeterminado.Antes de ClickHouse 26.7, los valores predeterminados de actions, compact y pretty eran 0. Aún puede obtener esa salida configurando explain_query_plan_default = 'legacy' (globalmente o en SETTINGS por consulta), o configurando compatibility en cualquier versión anterior a 26.7.Las opciones json y distributed no habilitan los valores predeterminados de pretty (actions, compact y pretty), incluso cuando explain_query_plan_default = 'pretty'. Para incluir detalles de las acciones en su salida, configure actions = 1 manualmente.No se admite la estimación del costo de los pasos ni de la consulta.
json = 1, el plan de consulta se representa en formato JSON. Cada nodo es un diccionario que siempre tiene las claves Node Type, Node Id y Plans. Node Type es una cadena con el nombre del paso, y Node Id es un identificador único del paso (el nombre del paso con un sufijo numérico, p. ej. Union_10). Plans es un array con descripciones de pasos secundarios. Pueden añadirse otras claves opcionales según el tipo de nodo y la configuración.
Ejemplo:
description = 1, se añade la clave Description al paso:
header = 1, la clave Header se agrega al paso como un array de columnas.
Ejemplo:
indexes = 1, se añade la clave Indexes. Contiene un array de los índices usados. Cada índice se describe como JSON con la clave Type (una cadena Partition Min-Max, Partition, Statistics, PrimaryKey o Skip) y claves opcionales:
El índice Statistics usa estadísticas de columna por parte (valores mínimos/máximos y el número de valores NULL en columnas Nullable) para omitir las partes que no pueden coincidir con el filtro de la consulta.
Name— El nombre del índice (actualmente solo se usa para índicesSkip).Keys— El array de columnas que usa el índice.Condition— La condición usada.Description— La descripción del índice (actualmente solo se usa para índicesSkip).Parts— El número de partes después/antes de aplicar el índice.Granules— El número de gránulos después/antes de aplicar el índice.Ranges— El número de rangos de gránulos después de aplicar el índice.
projections = 1, se añade la clave Projections. Contiene un array de proyecciones analizadas. Cada proyección se describe como JSON con las siguientes claves:
Name— El nombre de la proyección.Condition— La condición de la clave primaria usada por la proyección.Description— La descripción de cómo se usa la proyección (p. ej., filtrado a nivel de partes).Selected Parts— Número de partes seleccionadas por la proyección.Selected Marks— Número de marcas seleccionadas.Selected Ranges— Número de rangos seleccionados.Selected Rows— Número de filas seleccionadas.Filtered Parts— Número de partes omitidas debido al filtrado a nivel de partes.
actions = 1, las claves añadidas dependen del tipo de paso.
Ejemplo:
compact = 0 y actions = 1, se pueden ver los pasos Expression junto con información detallada sobre las expresiones:
distributed = 1, la salida incluye no solo el plan de consulta local, sino también los planes de consulta que se ejecutarán en los nodos remotos. Esto resulta útil para analizar y depurar consultas distribuidas.
distributed se representa solo en la forma legacy (no pretty), porque la salida pretty no integra los planes de los segmentos remotos en el árbol del plan. Por este motivo, habilitar distributed desactiva automáticamente los valores predeterminados de pretty (actions, compact y pretty), independientemente de explain_query_plan_default. Aun así, puede establecer actions=1 manualmente. La opción distributed tampoco se admite junto con json.pretty = 1, el árbol del plan se muestra con caracteres de dibujo de líneas en lugar de sangría, y se muestra información adicional para los pasos clave:
- Las columnas de salida de la consulta se imprimen en la parte superior del plan.
- Las expresiones en filtros, claves de agregación, descripciones de ordenación y funciones de ventana se muestran en una notación legible para humanos similar a SQL (por ejemplo,
a + 1 > 5en lugar degreater(plus(a, 1), 5)). Los prefijos internos de los identificadores de columna (como__table1.) se eliminan para mayor claridad. - Los pasos de origen (como
ReadFromMergeTree) muestran sus columnas de salida. - Los pasos de filtro muestran la condición de filtro en notación SQL. Cuando hay filtros de join en tiempo de ejecución, se muestran por separado.
- Los pasos de agregación muestran las claves y las funciones de agregación con sus argumentos (por ejemplo,
sum(c),count()). - Los conjuntos
INde literales de tupla muestran sus valores (truncados para conjuntos grandes), los conjuntos basados en subconsultas se etiquetan comosubquery1,subquery2, etc., y los conjuntos de tablas con motorSetmuestran el nombre de la tabla. - Los pasos de join muestran la relación de join mediante notación matemática, las estimaciones que el optimizador de orden de joins produjo para el paso (coste, selectividad, filas de salida y filas por lado), y las columnas de entrada de cada lado. Se usan los siguientes símbolos para representar distintos tipos de join:
Por ejemplo,
t1 ⟕ t2 significa un left join entre las tablas t1 y t2.
El número entre corchetes después del nombre de la tabla (por ejemplo, t1[100]) indica el número estimado de filas
cuando hay estadísticas de tabla disponibles.
Debajo de la relación de join, cada paso de join imprime las estimaciones que el optimizador de orden de joins produjo para él:
Cost— el coste de todo el subárbol de join bajo este paso, que es el valor que el optimizador minimiza al comparar los órdenes de join candidatos. El coste de un join es su número estimado de pares de filas coincidentes,<selectivity> * <left_rows> * <right_rows>, más el coste de sus entradas.Selectivity— la fracción estimada del producto cartesiano de ambos lados que sobrevive a la condición de join. Se deriva del número de valores distintos (NDV) de las claves de join: una igualdad de claves conserva aproximadamente1 / max(NDV_left, NDV_right)de los pares, y se usa la fracción más pequeña de entre las condiciones de join.Output rows— el número estimado de filas que produce el join:<selectivity> * <left_rows> * <right_rows>para un INNER JOIN, con un mínimo de<left_rows>paraLEFT, de<right_rows>paraRIGHTy de<left_rows> + <right_rows>paraFULL, porque un OUTER JOIN conserva todas las filas de su lado preservado. Cuando un joinSEMIoANTIparticipa en el reordenamiento, se estima como una fracción de su lado preservado:<preserved_rows> * min(1, <selectivity> * <other_rows>)paraSEMIy las filas restantes paraANTI; en caso contrario, se usa la fórmula de su tipo de join.Left/Right— el número estimado de filas que entran en el join desde cada lado.
no stats. Esto ocurre cuando la
optimización del orden de join no se ha ejecutado —por ejemplo, cuando
query_plan_optimize_join_order_limit
es 0— o cuando no hay base para la estimación.
Las líneas Input (left): e Input (right): enumeran las columnas que cada lado aporta al join.
La opción pretty funciona bien junto con compact = 1, que oculta los pasos Expression y la información detallada de las acciones, lo que facilita la lectura del plan.
Un ejemplo detallado con joins. La configuración
join_runtime_filter_min_probe_rows
se reduce únicamente para que una tabla tan pequeña siga construyendo un filtro de join en tiempo de ejecución:
EXPLAIN PIPELINE
Configuración:header— Muestra el encabezado de cada puerto de salida. Valor predeterminado: 0.graph— Muestra un grafo descrito en el lenguaje de descripción de grafos DOT. Valor predeterminado: 0.compact— Muestra el grafo en modo compacto si la configuracióngraphestá habilitada. Valor predeterminado: 1.compact_repeated_processor_chains— Compacta las cadenas repetidas de procesadores adyacentes en la salida de texto mostrando una sola copia de la cadena junto con el número de repeticiones. Esto puede facilitar la lectura de las canalizaciones paralelas cuando la misma cadena aparece muchas veces, por ejemplo, en joins. No afecta a la salida del grafo. Valor predeterminado: 0.
compact=0 y graph=1, los nombres de los procesadores contendrán un sufijo adicional con un identificador único de procesador.
Ejemplo:
EXPLAIN ANALYZE
EXPLAIN ANALYZE sí ejecuta la consulta, descarta las filas del resultado y muestra el mismo árbol del plan que EXPLAIN PLAN, con cada paso anotado con lo que realmente ocurrió en tiempo de ejecución.
Configuración:
EXPLAIN ANALYZE acepta las mismas opciones de visualización que EXPLAIN PLAN (documentadas en la sección EXPLAIN PLAN).
header— consulte la sección EXPLAIN PLAN.description— consulte la sección EXPLAIN PLAN.projections— consulte la sección EXPLAIN PLAN.sorting— consulte la sección EXPLAIN PLAN.input_headers— consulte la sección EXPLAIN PLAN.column_structure— consulte la sección EXPLAIN PLAN.actions— consulte la sección EXPLAIN PLAN. Valor predeterminado: 1.indexes— consulte la sección EXPLAIN PLAN. Valor predeterminado: 1.compact— consulte la sección EXPLAIN PLAN. Valor predeterminado: 1.pretty— consulte la sección EXPLAIN PLAN. Valor predeterminado: 1.processors— EnEXPLAIN ANALYZE, muestra una línea adicional por etapa con la distribución del tiempo transcurrido por procesador:min,median,maxysum. Es útil para detectar desequilibrios de carga entre procesadores paralelos. Valor predeterminado: 0.matches— EnEXPLAIN ANALYZE, hace que los pasos de join realicen el registro adicional necesario para las métricasmatched,match rateyfanouten los casos en que esos valores no puedan derivarse de lo que produce el join. Cuando pueden derivarse, se informan sin esta opción. Consulte Pasos de join. Valor predeterminado: 0.time— EnEXPLAIN ANALYZE, muestra líneasTimeyConcurrencypor paso con el tiempo de reloj y el nivel de concurrencia del paso y de su rama. Consulte Tiempo de reloj y niveles de concurrencia de pasos y ramas. Valor predeterminado: 1.
Como
EXPLAIN ANALYZE realmente ejecuta la consulta encapsulada, se comporta como esa
consulta — y, a diferencia de las formas EXPLAIN que no ejecutan — de varias maneras:- Cuotas y límites. Se contabiliza en las mismas cuotas
y está sujeto a los mismos límites
(por ejemplo,
query_selects,read_rows) que ejecutar la consulta directamente. Las fuentes exentas de cuotas durante la planificación (comosystem.one) no se contabilizan. - Transacciones fallidas. Dentro de una transacción
que ya falló (
ROLLED_BACK), se rechaza conINVALID_TRANSACTION, igual que unSELECTsimple: primero ejecuteROLLBACK. - Lecturas en streaming. En una lectura en streaming (
FROM ... STREAM), se rechaza conNOT_IMPLEMENTED, porque esa lectura nunca finaliza. - Consultas distribuidas. No es compatible con consultas ejecutadas en modo distribuido.
Time— tiempo total dividido en las fases de planificación (es decir, creación del plan + optimización del plan + construcción de la canalización) y ejecución (ejecución de la canalización).Execution— no se muestra cuandotime = 0. Desglosa el tiempo de ejecución enin steps,outside stepseidle. La suma de estas partes equivale aexecution. Consulte Tiempo de reloj y niveles de concurrencia de los pasos y las ramas.Read— filas y bytes sin comprimir leídos de las tablas, junto con el rendimiento: los mismos valores que el pie de la consulta normal muestra como “Processed”.Peak memory— memoria máxima utilizada por la consulta.
I/O). El tiempo y el paralelismo se informan por etapa del paso en las siguientes líneas con sangría.
rows <in> → <out>— filas que entraron y salieron del paso; (<selectivity>%) muestra cuánto filtró (out/in) o expandió los datos el paso; se oculta cuando las filas de entrada son iguales a las de salida y cuando las filas de entrada son iguales a0.<bytes_in> → <bytes_out>— bytes en memoria sin comprimir que fluyen por el paso (se omite cuando ambos son cero).time <t> (<share>%)— tiempo de reloj durante el que la etapa estuvo activa y su proporción del tiempo de ejecución de la consulta (es decir, sin incluir el tiempo de build). Tenga en cuenta que las proporciones pueden sumar más del 100% porque las etapas y los pasos se ejecutan de forma concurrente.parallelism <avg>/<max>— número medio de hilos de CPU trabajando a la vez dentro de esta etapa, sobre el máximo que podía usar. Esta métrica se limita a la etapa: no cuenta los hilos que trabajan al mismo tiempo en otros pasos de la consulta. Un valor cercano al máximo significa que la etapa estuvo bien paralelizada; un valor cercano a 1 significa que se ejecutó casi siempre en serie.Stage (<stage>)— el nombre de la etapa. Un paso con una sola etapa imprime la línea de tiempo directamente, sin una etiquetaStage (...). Los pasos con varias etapas imprimen una línea etiquetada por etapa; por ejemplo,AggregatingmuestraStage (partial aggregation)yStage (final aggregation), y un hash join muestraStage (build)yStage (probe).
ClickHouse paraleliza no solo la ejecución de tareas dentro de un paso del plan, sino también la ejecución de los propios pasos del plan. La métrica
parallelism refleja solo el trabajo de este paso. Otros pasos pueden ejecutarse de forma concurrente, por lo que este valor no muestra cómo se compara el paralelismo del paso con el de toda la consulta. Para ver la concurrencia de toda la consulta, consulte la métrica Concurrency que se muestra con time = 1 (Tiempo de reloj y niveles de concurrencia de pasos y ramas).El valor máximo en
parallelism se calcula como el mínimo entre:- el número total de tareas dentro del paso del plan;
- el número máximo de hilos de procesamiento de consultas configurado en
max_threads.
Pasos de unión
En un paso de unión,EXPLAIN ANALYZE muestra líneas que comparan las estimaciones del optimizador de orden de unión con lo que ocurrió realmente (consulte Métricas de unión estimadas frente a reales) y líneas de participación para cada lado —Left y Right—, seguidas de las líneas específicas de la implementación de la unión. Left y Right corresponden a los lados lógicos de SQL. En la mayoría de los casos, Left también será el lado de sondeo (probe) de la unión y Right, el lado de construcción (build). Sin embargo, no siempre es así, debido al intercambio que puede producirse durante la ejecución de la unión. Se cubren todos los valores de join_algorithm (hash, parallel_hash, grace_hash, partial_merge, full_sorting_merge, parallel_full_sorting_merge, direct), así como las tres implementaciones que esta configuración no puede seleccionar: una unión CROSS o COMMA, la unión block nested loop que recibe una sección ON que no determina ninguna clave de unión y el motor de tabla Join. La mayoría informa sobre ambos lados; algunas solo informan del lado que materializan (por ejemplo, direct solo muestra Left:).
Las líneas de cada lado tienen la misma estructura:
EXPLAIN ANALYZE informa de lo siguiente:
rows estimated <estimated_rows>— la estimación del optimizador de orden de unión sobre las filas de ese lado, mostrada para compararla con el valor real derowsque aparece junto a ella;no statscuando el optimizador no produjo ninguna estimación (consulte Métricas de unión estimadas frente a reales).rows <rows>— el número total de filas de ese lado que pasaron por la unión.matched <matched_rows>— el número de filas de ese lado que encontraron al menos una fila correspondiente en el otro lado de la unión. Esto cuenta filas, no claves: si una clave aparece tres veces en el lado derecho y coincide, las tres filas de ese lado se cuentan como coincidentes.match rate <match_rate>%— el porcentaje de filas de ese lado que coincidieron, calculado como100 * <matched_rows> / <rows>.fanout <fanout>— el número de filas de salida que produjo, en promedio, una fila coincidente de ese lado.
not collected en lugar de 0. match rate y fanout se derivan de matched, por lo que, si un lado no dispone de este valor, los tres se informan como not collected.
Métricas estimadas frente a reales del join
Un paso de join incluye las estimaciones del optimizador de orden de join —las mismas que muestraEXPLAIN PLAN (consulta la sección EXPLAIN PLAN)— y EXPLAIN ANALYZE imprime cada una de ellas junto al valor medido:
Cost— ambos valores cuentan las filas de salida coincidentes. La estimación es el coste que el optimizador asigna al subárbol del join:<selectivity> * <left_rows> * <right_rows>más el coste de sus entradas. El valor real se mide de la misma forma: las filas de salida coincidentes de este join más el coste real de todos los joins situados por debajo que pertenezcan al mismo cluster de reordenación.Selectivity— la estimación se deriva del número de valores distintos de las join keys; el valor real es la fracción medida del producto cartesiano que acabó en la salida:<matched output rows> / (<left rows> * <right rows>), ya que es justo lo que el optimizador intenta estimar.Output rows— el número estimado y el real de filas que produjo el join. Cuando ambos son distintos de cero,q-errorinforma demax(estimated / actual, actual / estimated), la medida estándar de la calidad de la estimación de cardinalidad:1.00indica una estimación perfecta, y un valor elevado indica que el optimizador eligió el join order a partir de una cardinalidad gravemente errónea.
no stats; por ejemplo, cuando la optimización del join order no se ejecutó porque query_plan_optimize_join_order_limit es 0. Esto es distinto de not collected, que marca un valor real que la ejecución no pudo medir. Los joins con un lado derecho precargado (el motor de tabla Join, los joins direct) no pasan por el optimizador de orden de join e imprimen únicamente las líneas de participación.
El paso de join también imprime las líneas Input (left): e Input (right): con las columnas que cada lado aporta al join.
Para las tablas del ejemplo de join de EXPLAIN PLAN, el paso de join de EXPLAIN ANALYZE SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id tiene este aspecto:
Fanout
fanout mide la multiplicación de filas:
outer join genera una fila de salida rellenada con NULL por cada fila del lado preservado que no encontró correspondencia. Estas filas se restan para que no distorsionen la proporción. Solo el lado preservado las tiene: el derecho para RIGHT y FULL, y el izquierdo para LEFT y FULL:
fanout = 0— las filas coincidentes no generaron ninguna fila de salida, como ocurre con un joinANTI: solo genera las filas que no encontraron correspondencia.fanout = 1— un join 1:1 sin duplicados; cada fila coincidente generó exactamente una fila de salida.fanout > 1— un join 1:N; las claves duplicadas del otro lado multiplicaron las filas. Un valor alto en ambos lados a la vez indica una explosión cartesiana no intencionada.
Cuándo los números requieren matches = 1
La mayoría de estos números se obtienen de datos que el join ya genera de todos modos y se muestran con un EXPLAIN ANALYZE normal. El resto requiere un seguimiento que el join no realizaría de otro modo, por lo que solo se muestra con EXPLAIN ANALYZE matches = 1. Cuáles son depende del algoritmo; en la familia hash hay dos casos:
- el lado derecho de
ALL INNERyALL LEFT, que requiere marcar cada fila derecha coincidente; - el lado izquierdo de
ALL LEFTyALL FULL, pero solo cuando la consulta no selecciona nada de la tabla derecha y la secciónONes una igualdad de clave simple. De lo contrario, el sondeo ya registra qué filas izquierdas coincidieron, ya sea para materializar las columnas derechas o para evaluar la condición residual, y el recuento es exacto sin la opción.
partial_merge lo necesita para el lado derecho de los cuatro tipos ALL, por el mismo motivo.
full_sorting_merge y parallel_full_sorting_merge lo necesitan para ambos lados de los tipos ANY. Los
tipos ALL no necesitan nada.
La opción está desactivada de forma predeterminada porque el seguimiento adicional no es gratuito: su coste es la fidelidad de la medición. El trabajo se realiza dentro del bucle de sondeo y aumenta con el número de filas de salida. Use
matches = 1 cuando necesite conocer las coincidencias exactas que las filas izquierdas y derechas encontraron en el otro lado.matches = 1 no permite recopilar todas las combinaciones. El lado sobre el que un join puede informar depende de lo que ese join debe hacer de todos modos, por lo que también depende del algoritmo, además del tipo y la strictness.
Familia hash. hash, parallel_hash y grace_hash siempre se comportan igual:
El lado derecho no está disponible cuando el join conserva solo una fila por clave en su tabla hash, como hacen los joins
ANY, SEMI y ANTI: las filas derechas duplicadas nunca se almacenan, por lo que no pueden contarse. El lado izquierdo no está disponible cuando el join suprime la salida de una fila izquierda cuya fila asociada ya fue reclamada por otra fila izquierda, lo que hace que las filas emitidas infravaloren el número de filas coincidentes.
Al habilitar any_join_distinct_right_table_keys, ANY cambia a la semántica anterior de RightAny, que emite una fila por cada fila izquierda y, por tanto, conserva ambos recuentos. ANY RIGHT y ANY FULL informan entonces de ambos lados, y ANY INNER se reescribe como SEMI LEFT.
El motor de tabla Join sigue la misma tabla y usa el tipo y la strictness declarados en el motor: Join(ALL, INNER, …) informa de ambos lados y Join(ANY, LEFT, …), de ninguno.
Algoritmos de fusión. full_sorting_merge y parallel_full_sorting_merge aceptan los cuatro tipos ALL, ANY INNER, ANY LEFT, ANY RIGHT, ASOF y ASOF LEFT. Informan de ambos lados para todos los tipos excepto ASOF y ASOF LEFT, donde el lado derecho es not collected, y lo hacen sin matches = 1: recorren las dos entradas ordenadas y ven cada fila de un rango de igualdad a medida que lo consumen, por lo que no es necesario reconstruir nada después.
partial_merge acepta ALL INNER, ALL LEFT, ALL RIGHT, ALL FULL, ANY INNER, ANY LEFT y SEMI LEFT. Informa de ambos lados para los cuatro tipos ALL, del derecho con matches = 1; para ANY INNER, ANY LEFT y SEMI LEFT, el lado derecho es not collected.
direct. Solo el lado izquierdo. El lado derecho es un almacén de clave-valor que nunca se materializa en filas, por lo que no tiene ninguna línea Right:.
CROSS, COMMA y un ON constante. Ningún lado, como se describe anteriormente.
Block nested loop join. Es el operador que recibe una sección ON que no determina ninguna join key, salvo que alguna de las rutas anteriores reclame la condición (consulte allow_block_nested_loop_join). Acepta todos los tipos y estrictez excepto ASOF, PASTE y ANY FULL:
El lado izquierdo se informa sin la opción siempre que el operador ya registre qué filas izquierdas tuvieron coincidencia: todo tipo que conserve las filas izquierdas sin coincidencia necesita ese registro, al igual que toda estrictez que tome como máximo un par por fila izquierda. El lado derecho se informa siempre que el operador conserve sus indicadores de coincidencia, que es lo que necesitan
RIGHT, FULL y un SEMI/ANTI dirigido por la derecha. ANY INNER también los conserva, pero solo para asignar cada fila derecha a una única fila izquierda: deja de reclamar para una fila izquierda que ya tiene su par, por lo que los indicadores marcan los pares del resultado y no todas las coincidencias, y no se informan. ALL INNER solo llega al operador cuando hash está deshabilitado, ya que, de lo contrario, la condición se convierte en un CROSS JOIN con un filtro.
Al habilitar any_join_distinct_right_table_keys, ANY pasa también aquí a la semántica RightAny, que informa el lado izquierdo para todos los tipos y el lado derecho para RIGHT y FULL; ANY INNER se reescribe como SEMI LEFT.
Cuando dos algoritmos informan un mismo número, los valores coinciden. Los algoritmos de fusión simplemente disponen de más información; no discrepan sobre qué constituye una coincidencia.
Líneas específicas del algoritmo
Veamos las líneas que añade cada implementación de unión además de las anteriores. En las unioneshash y parallel_hash, y en el motor de tabla Join, una línea Hash table: describe la tabla hash creada a partir de la tabla derecha:
unique keys <unique_keys>— el número de claves únicas almacenadas en la tabla hash durante la fase de construcción.memory <peak_memory>— el pico de memoria utilizado por la tabla hash durante la fase de construcción.
grace_hash, la línea Hash table: también indica cómo se adaptó la unión al límite de memoria, y la línea Spill: indica si los datos se volcaron al disco:
buckets <buckets>— el número de buckets que tuvo la unión grace hash al finalizar la ejecución. Siempre es una potencia de2.rehashes <rehashes>— cuántas veces fue necesario duplicar el número de buckets para ajustarse al límite de memoria.Spill:— un indicadoryes/noque señala si se produjo algún volcado a disco. Si lo hubo,left spilled <left_spilled_bytes>yright spilled <right_spilled_bytes>indican los bytes comprimidos volcados de los lados izquierdo (sondeo) y derecho (construcción), respectivamente; si no se volcó nada, la línea es simplementeSpill: no.
partial_merge, la línea Right: incluye información adicional sobre cómo se almacenó temporalmente en el búfer y se ordenó la tabla derecha, y el tiempo de ordenación se muestra en las líneas Stage (build) y Stage (probe):
size <right_size>— el tamaño en memoria de los bloques de la tabla derecha.blocks <right_blocks>— el número de bloques en los que se almacenó temporalmente en el búfer la tabla derecha.storage <in-memory|external>— indica si la tabla derecha cabía en memoria (in-memory) o tuvo que volcarse al disco (external). Cuando esexternal, un campo adicionalspilled <spilled_bytes>informa de los bytes comprimidos escritos en disco.sort time <sort_time>— el tiempo empleado en ordenar la tabla derecha (en la etapa de construcción) y cada bloque entrante de la izquierda (en la etapa de sondeo).sort share <sort_share>%—sort timecomo proporción de la propia actividad de esa etapa (la suma del tiempo transcurrido de sus procesadores), a diferencia del porcentajetimede la etapa, que representa una proporción del tiempo de ejecución de toda la consulta.
full_sorting_merge, solo se muestran las líneas comunes Left: y Right:.
Para la unión direct, solo se muestra la línea Left:, ya que el lado derecho es un almacén de clave-valor que se consulta directamente en lugar de materializarse en filas.
Para una unión CROSS o COMMA, y para un ALL INNER JOIN cuya sección ON no contiene una igualdad de clave —que el planificador convierte en un CROSS JOIN con esa condición como filtro—, una línea Buffer: describe cómo se mantuvo la tabla derecha en memoria y una línea Spill: informa de si se volcó al disco:
memory <peak_memory>— el pico de memoria que ocupó la tabla derecha almacenada temporalmente en el búfer.compressed <yes|no>— indica si se comprimió al menos un bloque almacenado temporalmente en el búfer; en ese caso, los lectores descomprimen todos los bloques almacenados.Spill:— el mismo indicadoryes/noque paragrace_hash;right spilled <right_spilled_bytes>indica los bytes comprimidos escritos en disco.
matched not collected aquí: un predicado constante empareja todas las filas izquierdas con todas las filas derechas, o ninguna, por lo que no se puede indicar qué filas individuales coincidieron.
La unión block nested loop, que se encarga de todas las demás secciones ON que no tienen una igualdad de claves, imprime las mismas dos líneas sobre la tabla derecha que materializa:
memory <peak_memory>— la memoria máxima que ocupó la tabla derecha almacenada. Es0cuando cada bloque se escribió en disco a medida que llegaba, ya que ninguno llegó a mantenerse en memoria.compressed <yes|no>— indica si al menos un bloque almacenado se comprimió; en ese caso, los lectores descomprimen todos los bloques almacenados.Spill:— el mismo indicadoryes/noque engrace_hash, donderight spilled <right_spilled_bytes>indica los bytes comprimidos escritos en disco.
CROSS, aquí ambos lados pueden informar matched, ya que el operador evalúa una condición real en cada par que examina; en la tabla anterior se indica cuál de ellos lo hace.
Para una unión con el motor de tabla Join, se muestran ambos lados, junto con la línea Hash table:, que describe la tabla preconstruida. El lado derecho cuenta las filas almacenadas en el motor, no las filas de una compilación específica de cada consulta.
Tiempos por procesador
Conprocessors = 1, se imprime una línea adicional debajo de cada etapa que muestra la distribución del tiempo transcurrido entre los procesadores de la etapa:
<n> es el número de procesadores de la etapa. Una gran diferencia entre median y max apunta a un desequilibrio de carga entre los procesadores paralelos.
Tiempo de reloj y niveles de concurrencia por paso y rama
Salvo quetime = 0, se imprimen dos líneas adicionales por cada paso del plan de consulta:
step se refiere al paso en sí. branch se refiere al subárbol que tiene ese paso como raíz: el propio paso y todos los que están por debajo de él en el plan.
Time— el tiempo de reloj durante el cual al menos un thread trabajó en el paso (step) o en cualquier paso del subárbol (branch).<share>expresa ese tiempo como porcentaje del tiempo de ejecución de la consulta.Concurrency— el número medio de threads ocupados en cualquier paso de la consulta —no solo en este— mientras el paso (step) o su subárbol (branch) estaban activos.<m>es el número máximo de threads que podía utilizar la consulta. Los pasos sin procesadores propios, comoUnioncuando sus entradas ya tienen el mismo encabezado, muestranUnknownparastep: no hay trabajo que medir. El valor debranchsí se muestra.
Time de un paso es la duración de la unión de sus intervalos. Los huecos, durante los cuales ningún thread trabajó en el paso, no se cuentan. Para calcular el valor de Time de una rama, primero se unen los intervalos de todos los pasos del subárbol.
El branch del paso raíz abarca todo el plan, pero su porcentaje suele ser inferior al 100 %. La línea Execution del resumen de la consulta muestra en qué se empleó el resto del tiempo de ejecución (consulte el resumen de la consulta en el ejemplo anterior):
in steps— la unión de los intervalos de todos los pasos del plan. Equivale al tiempo debranchdel paso raíz, y su porcentaje es el mismo.outside steps— el tiempo durante el cual al menos un thread ejecutó un procesador, pero ninguno de los procesadores en ejecución pertenecía a un paso de este plan: el sumidero de salida,Resize, las transformaciones de conversión y los pasos de planes a los que no se puede llegar desde este.idle— el tiempo durante el cual ningún thread ejecutó un procesador: el arranque y la detención del ejecutor, así como los momentos en que todos los threads estaban esperando.
Time, los porcentajes de time por etapa y los porcentajes de Execution utilizan el mismo denominador, por lo que pueden compararse entre sí.
Concurrency es el tiempo total de actividad de todos los threads de la consulta dentro de esa unión, dividido entre la duración de la unión. Cuenta los threads ocupados en cualquier paso de la consulta, a diferencia de la métrica parallelism por etapa, que solo cuenta los threads de esa etapa. Responde a la pregunta: “mientras este paso (o esta rama) estaba activo, ¿hasta qué punto estuvo ocupada la consulta en su conjunto?” Un valor cercano a <m> significa que la consulta mantuvo el paralelismo máximo durante la duración de ese paso. Un valor cercano a 1 significa que, durante ese tiempo, el trabajo de la consulta se ejecutó en serie: en el caso de step, el único thread ocupado trabajaba en el propio paso, lo que lo convierte en un punto de serialización; en el caso de branch, el thread trabajaba en algún lugar del subárbol, no necesariamente en este paso. Un paso serializado solo constituye un verdadero cuello de botella si su porcentaje de Time también es significativo.
EXPLAIN ESTIMATE
Muestra el número estimado de filas, marcas y partes que se leerán de las tablas al procesar la consulta. Funciona con tablas de la familia MergeTree. Ejemplo Crear una tabla:Query
Query
Response
EXPLAIN WHATIF
Estima el beneficio que tendría un índice de omisión hipotético en una consultaSELECT, sin materializar el índice en disco. Defina uno o más candidatos con CREATE HYPOTHETICAL INDEX y, a continuación, ejecute EXPLAIN WHATIF SELECT ... para ver, para cada candidato, su aplicabilidad, las marcas leídas estimadas, los bytes estimados y la proporción de omisión.
Las proyecciones hipotéticas definidas con CREATE HYPOTHETICAL PROJECTION también son candidatas. En el caso de una proyección normal, la estimación construye su índice primario en memoria sobre las partes que leería la consulta y lo poda del mismo modo en que se podaría una proyección materializada.
El informe indica las marcas y filas que leería, un read_ratio respecto a la lectura de la tabla base y un verdict sobre si el optimizador la elegiría. Con force_optimize_projection = 1 o prefer_optimize_projection = 1, el optimizador utiliza cualquier proyección utilizable, por lo que una proyección que no elegiría por coste se notifica como chosen (forced).
Cuando el resultado depende de cómo se dispondría la parte de la proyección, marks_span indica el rango, y una decisión que podría decantarse hacia cualquiera de los dos lados se notifica como too close to call. En tablas grandes, la estimación lee una muestra de gránulos (consulte projection_scan_budget_rows).
Las proyecciones de agregación, las proyecciones con una cláusula WHERE, con índices de omisión propios, con una clave basada en el orden de commit o en columnas virtuales, o con un _block_number almacenado, así como las proyecciones que no cubren todas las columnas que lee la consulta, se notifican como not_applicable. Se ignoran force_optimize_projection_name y preferred_optimize_projection_name, y force_optimize_projection no hace que la sentencia falle.
Sintaxis
projection_scan_budget_rows— número de filas que puede leer la estimación de una proyección antes de recurrir a una muestra de gránulos. Valor predeterminado:10000000.0significa sin límite;max_rows_to_readpuede reducirlo. Una muestra lee al menos unos 30 gránulos y uno por parte, y cuandomax_rows_to_readno lo permite, la estimación esunsupported.empirical—1(valor predeterminado) evalúa el índice en memoria sobre los gránulos podados por la línea base para medir la proporción de gránulos que se pueden omitir (un límite superior).0omite este procedimiento. Si la evaluación empírica no produce un resultado (porque está deshabilitada o porque el índice no puede evaluarse en memoria), el estimador recurre a las estadísticas de columna. Si tampoco están disponibles, ofrece un resumen que solo indica si el índice es aplicable.
source— cómo se generó la estimación.empirical: construyó el índice en memoria sobre los gránulos podados por la línea base y contó los gránulos que el índice omitiría. Este es un límite superior; consulta las limitaciones enCREATE HYPOTHETICAL INDEX.statistical: se deriva de las estadísticas de columnas. Se usa cuandoempiricalestá deshabilitado (empirical = 0) o cuandoempiricalno pudo producir un resultado, y hay estadísticas de columnas definidas en las columnas relevantes.applicability_only: el índice es aplicable al predicado, pero ni la estimaciónempiricalni lastatisticalprodujeron un resultado (p. ej.,empirical = 0y no hay estadísticas de columnas definidas). Informaskip_ratio: 0.0%como límite conservador.
empirical_reason— por qué no pudo ejecutarse la estimación empírica. Se muestra solo conempirical_status: unsupported. Por ejemplo, un valor distinto de cero paramerge_tree_min_rows_for_seekomerge_tree_min_bytes_for_seekhace que una lectura real fusione intervalos de marcas, algo que el recuento por gránulo no modela, por lo que la estimación recurre astatisticaloapplicability_only.sampled_parts/sampled_marks—<baseline-pruned> / <total in the table>. Muestra qué fracción de la tabla quedó tras la poda por PK, partición e índices existentes; es decir, la entrada del índice hipotético. Para una proyección,sampled_markses<marks read> / <marks in the parts the query reads>, y un primer número menor que el segundo indica que la estimación usó una muestra.est_bytes— una estimación de los bytes leídos, derivada del tamaño medio de fila de la tabla, por lo que es aproximada y varía según el almacenamiento y la compresión. La línea de base aparece solo cuando la consulta lee filas; la línea por candidato, solo cuando se conoce la estimación de bytes de la línea de base.
WHATIF y SELECT; no hay palabra clave SETTINGS (esto coincide con cómo otras variantes de EXPLAIN aceptan sus opciones).
Si no hay índices hipotéticos ni proyecciones hipotéticas definidos para la tabla, EXPLAIN WHATIF informa status: not_applicable con una sugerencia para crear uno.
Fila combinada (múltiples candidatos)
Cuando se evalúan empíricamente dos o más candidatos, EXPLAIN WHATIF añade un bloque adicional llamado (combined: idx_a, idx_b, ...) después de las filas de cada candidato. Muestra el beneficio conjunto de disponer de todos esos índices a la vez: una lectura real conserva un gránulo solo si no lo descarta ninguno de los índices de omisión. Por tanto, la estimación combinada se basa en la intersección de los gránulos que conserva cada candidato. Su skip_ratio es, como mínimo, tan alto como el del mejor candidato individual: los índices complementarios podan más gránulos en conjunto, mientras que los redundantes no modifican la tasa.
Solo contribuyen los candidatos con source: empirical, porque la fila combinada se construye mediante la intersección de los conjuntos de gránulos que conserva cada uno. Los candidatos estimados como statistical o applicability_only no tienen datos por gránulo y quedan excluidos; por tanto, el bloque combinado solo aparece cuando al menos dos candidatos han producido una estimación empírica y se omite en caso contrario (por ejemplo, con empirical = 0). Sus campos de estimación se interpretan igual que los de un bloque empírico por candidato, excepto que elapsed_us es 0: la estimación combinada se deriva de los escaneos de cada candidato, no de un escaneo adicional. El nombre sintético (combined: ...) es solo una etiqueta del informe y no puede usarse con force_data_skipping_indices.
Ejemplo empírico
minmax podaría las marcas de 100 a 1, con un skip_ratio: 99.0%. (est_bytes se estima a partir del tamaño medio de fila, por lo que la cifra exacta puede variar).
Ejemplo con estadísticas
Las estadísticas de columna están desactivadas de forma predeterminada. Para usar la ruta statistical, defínalas primero en las columnas correspondientes y espere a que finalice la mutación de materialización:
b < 10 (aproximadamente 10 filas de 10000) y se muestra como un límite superior de skip_ratio. No hay sampled_parts / sampled_marks: no se leyó ningún dato.
Si ninguna de las dos vías está disponible (p. ej., empirical = 0 y no hay estadísticas de columna definidas), el estimador informa source: applicability_only y un skip_ratio: 0.0% conservador.
Proyecciones hipotéticas
Una proyección normal se estima del mismo modo, a partir de las partes que leería la consulta después de la poda por clave primaria y por partición:
marks y rows indican las marcas y filas que leería la propia proyección, no una parte de la lectura de la tabla base, ya que un gránulo de la proyección contiene filas distintas de las de un gránulo de la tabla base. read_ratio es la relación entre esas marcas y las de la tabla base: 0.02x equivale a cincuenta veces menos trabajo y 15x, a quince veces más. verdict aplica la regla del propio optimizador —menos marcas que la tabla base, o el mismo número si la proyección satisface el ORDER BY de la consulta—, por lo que un candidato puede ser applicable y aun así no resultar elegido.
EXPLAIN TABLE OVERRIDE
Muestra el resultado de una sobrescritura de tabla aplicada al esquema de una tabla a la que se accede mediante una función de tabla. También realiza ciertas validaciones y genera una excepción si la sobrescritura hubiera provocado algún tipo de fallo. Ejemplo Suponga que tiene una tabla MySQL remota como esta:Query
Query
Response
La validación aún no es exhaustiva, por lo que una consulta correcta no garantiza que la sobrescritura no vaya a causar problemas.