EXPLAIN — mostrar el plan de ejecución de una sentencia
EXPLAIN [ (opción[, ...] ) ]sentenciadondeopciónpuede ser uno de: ANALYZE [booleano] VERBOSE [booleano] COSTS [booleano] SETTINGS [booleano] GENERIC_PLAN [booleano] BUFFERS [booleano] SERIALIZE [ { NONE | TEXT | BINARY } ] WAL [booleano] TIMING [booleano] SUMMARY [booleano] MEMORY [booleano] FORMAT { TEXT | XML | JSON | YAML }
Este comando muestra el plan de ejecución que el planificador de PostgreSQL genera para la sentencia suministrada. El plan de ejecución muestra cómo se escanearán la(s) tabla(s) a la(s) que hace referencia la sentencia — mediante escaneo secuencial simple, escaneo de índice, etc. — y, si se hace referencia a varias tablas, qué algoritmos de unión (join) se utilizarán para reunir las filas requeridas de cada tabla de entrada.
La parte más crítica de la pantalla es el costo estimado de ejecución de la
sentencia, que es la estimación del planificador sobre cuánto tiempo tardará en
ejecutarse la sentencia (medido en unidades de costo que son arbitrarias, pero
que convencionalmente significan lecturas de páginas de disco). En realidad,
se muestran dos números: el costo de inicio (start-up cost) antes de que se pueda
devolver la primera fila, y el costo total para devolver todas las filas. Para
la mayoría de las consultas, lo que importa es el costo total, pero en contextos
como una subconsulta en EXISTS, el planificador elegirá el costo
de inicio más pequeño en lugar del costo total más pequeño (ya que el ejecutor
se detendrá después de obtener una fila, de todos modos). Además, si limitas
el número de filas a devolver con una cláusula LIMIT,
el planificador realiza una interpolación adecuada entre los costos de los
extremos para estimar qué plan es realmente el más barato.
La opción ANALYZE hace que la sentencia se ejecute
realmente, no solo se planifique. Luego, se añaden estadísticas de tiempo
de ejecución reales a la pantalla, incluyendo el tiempo total transcurrido
en cada nodo del plan (en milisegundos) y el número total de filas que realmente
devolvió. Esto es útil para ver si las estimaciones del planificador son
cercanas a la realidad.
Ten en cuenta que la sentencia se ejecuta realmente cuando
se utiliza la opción ANALYZE. Aunque
EXPLAIN descartará cualquier salida que devolvería un
SELECT, otros efectos secundarios de la sentencia
ocurrirán de manera habitual. Si deseas usar
EXPLAIN ANALYZE en una sentencia
INSERT, UPDATE,
DELETE, MERGE,
CREATE TABLE AS o EXECUTE
sin permitir que el comando afecte tus datos, utiliza este enfoque:
BEGIN; EXPLAIN ANALYZE ...; ROLLBACK;
ANALYZE
Lleva a cabo el comando y muestra los tiempos de ejecución reales y otras estadísticas.
Este parámetro tiene como valor por omisión FALSE.
VERBOSE
Muestra información adicional sobre el plan. Específicamente, incluye
la lista de columnas de salida para cada nodo en el árbol del plan, califica
los nombres de tablas y funciones con su esquema, etiqueta siempre las variables
en las expresiones con su alias de la tabla de rango, y muestra siempre el nombre
de cada disparador para el cual se muestran estadísticas. También se mostrará el
identificador de consulta si se ha calculado uno, consulta la compute_query_id para obtener más detalles. Este parámetro
tiene como valor por omisión FALSE.
COSTS
Incluye información sobre el costo de inicio estimado y el costo total de cada
nodo del plan, así como el número estimado de filas y el ancho estimado
de cada fila. Este parámetro tiene como valor por omisión TRUE.
SETTINGS
Incluye información sobre los parámetros de configuración. Específicamente, incluye
opciones que afectan la planificación de consultas con un valor diferente al valor
por omisión incorporado. Este parámetro tiene como valor por omisión FALSE.
GENERIC_PLAN
Permite que la sentencia contenga marcadores de posición de parámetros como
$1, y genera un plan genérico que no dependa de los
valores de esos parámetros. Consulta la PREPARE para obtener detalles
sobre los planes genéricos y los tipos de sentencias que admiten parámetros.
Este parámetro no se puede usar junto con ANALYZE.
Tiene como valor por omisión FALSE.
BUFFERS
Incluye información sobre el uso de búferes. Específicamente, incluye el número de
bloques compartidos (shared blocks) acertados (hit), leídos, modificados (dirtied)
y escritos, el número de bloques locales acertados, leídos, modificados y escritos,
el número de bloques temporales leídos y escritos, y el tiempo dedicado a leer y
escribir bloques de archivos de datos, bloques locales y bloques de archivos
temporales (en milisegundos) si track_io_timing
está activado. Un acierto (hit) significa que se evitó una
lectura porque el bloque ya se encontraba en caché cuando se necesitó.
Los bloques compartidos contienen datos de tablas e índices normales;
los bloques locales contienen datos de tablas e índices temporales;
mientras que los bloques temporales contienen datos de trabajo a corto plazo utilizados en
ordenamientos, hashes, nodos de plan Materialize y casos similares.
El número de bloques modificados (dirtied) indica el número de
bloques previamente no modificados que fueron cambiados por esta consulta; mientras que el
número de bloques escritos indica el número de bloques
previamente modificados que fueron desalojados de la caché por este backend durante
el procesamiento de la consulta.
El número de bloques mostrados para un nodo de nivel superior incluye los utilizados
por todos sus nodos hijos. En formato de texto, solo se imprimen los valores
distintos de cero. La información de los búferes se incluye automáticamente cuando
se utiliza ANALYZE.
SERIALIZE
Incluye información sobre el costo de serializar los
datos de salida de la consulta, es decir, convertirlos a formato de texto o
binario para enviarlos al cliente. Esto puede ser una parte significativa
del tiempo requerido para la ejecución normal de la consulta si las funciones
de salida del tipo de datos son costosas o si los valores TOAST
deben recuperarse del almacenamiento fuera de línea (out-of-line storage).
El comportamiento por omisión de EXPLAIN,
SERIALIZE NONE, no realiza estas conversiones.
Si se especifica SERIALIZE TEXT o
SERIALIZE BINARY, se realizan las conversiones correspondientes
y se mide el tiempo dedicado a ello (a menos que se especifique TIMING OFF).
Si también se especifica la opción BUFFERS, se contarán también los
accesos a los búferes implicados en las conversiones. En ningún caso, sin embargo,
EXPLAIN enviará realmente los datos resultantes al cliente;
por lo tanto, los costos de transmisión de red no se pueden investigar de esta manera.
La serialización solo se puede activar cuando ANALYZE también
está activado. Si se escribe SERIALIZE sin argumento, se asume
TEXT.
WAL
Incluye información sobre la generación de registros de WAL. Específicamente, incluye el
número de registros, el número de imágenes de página completa (fpi), la cantidad de WAL
generada en bytes y el número de veces que los búferes de WAL se llenaron.
En formato de texto, solo se imprimen los valores distintos de cero.
Este parámetro solo se puede utilizar cuando ANALYZE también está
activado. Tiene como valor por omisión FALSE.
TIMING
Incluye en la salida el tiempo de inicio real y el tiempo dedicado a cada nodo.
La sobrecarga de leer repetidamente el reloj del sistema puede ralentizar la
consulta significativamente en algunos sistemas, por lo que puede ser útil establecer este
parámetro en FALSE cuando solo se necesitan los conteos de filas reales y
no los tiempos exactos. El tiempo de ejecución de toda la sentencia se mide
siempre, incluso cuando el cronometraje a nivel de nodo se desactiva con esta
opción.
Este parámetro solo se puede utilizar cuando ANALYZE también está
activado. Tiene como valor por omisión TRUE.
SUMMARY
Incluye información de resumen (por ejemplo, información de tiempos totales) después del
plan de consulta. La información de resumen se incluye por omisión cuando se utiliza
ANALYZE, pero de lo contrario no se incluye por omisión, aunque se
puede activar mediante esta opción. El tiempo de planificación en
EXPLAIN EXECUTE incluye el tiempo requerido para recuperar
el plan de la caché y el tiempo requerido para la replanificación, si es necesario.
MEMORY
Incluye información sobre el consumo de memoria por parte de la fase de planificación de la consulta.
Específicamente, incluye la cantidad precisa de almacenamiento utilizado por las estructuras
en memoria del planificador, así como la memoria total considerando la sobrecarga de
asignación. Este parámetro tiene como valor por omisión FALSE.
FORMAT
Especifica el formato de salida, que puede ser TEXT, XML, JSON o YAML.
La salida que no es de texto contiene la misma información que el formato de salida
de texto, pero es más fácil de analizar para los programas. Este parámetro tiene
como valor por omisión TEXT.
booleano
Especifica si la opción seleccionada debe activarse o desactivarse.
Puedes escribir TRUE, ON o
1 para activar la opción, y FALSE,
OFF o 0 para desactivarla. El valor
booleano también se puede
omitir, en cuyo caso se asume TRUE.
sentencia
Cualquier sentencia SELECT, INSERT, UPDATE,
DELETE, MERGE,
VALUES, EXECUTE,
DECLARE, CREATE TABLE AS o
CREATE MATERIALIZED VIEW AS cuyo plan de ejecución
desees ver.
El resultado del comando es una descripción textual del plan seleccionado
para la sentencia,
opcionalmente anotada con estadísticas de ejecución.
La Section 14.1 describe la información proporcionada.
Para permitir que el planificador de consultas de PostgreSQL
tome decisiones razonablemente informadas al optimizar las consultas, los datos de
pg_statistic
deben estar actualizados para todas las tablas utilizadas en la consulta. Normalmente,
el demonio de autovacuum se encargará de ello
automáticamente. Pero si una tabla ha tenido cambios sustanciales recientemente en
su contenido, es posible que debas realizar un ANALYZE manual en lugar de esperar
a que autovacuum se ponga al día con los cambios.
Para medir el costo en tiempo de ejecución de cada nodo en el plan de ejecución,
la implementación actual de EXPLAIN ANALYZE añade una sobrecarga
de análisis de rendimiento (profiling) a la ejecución de la consulta. Como resultado,
ejecutar EXPLAIN ANALYZE en una consulta a veces puede tomar
significativamente más tiempo que ejecutar la consulta normalmente. La cantidad de
sobrecarga depende de la naturaleza de la consulta, así como de la plataforma
utilizada. El peor caso ocurre para los nodos del plan que en sí mismos requieren
muy poco tiempo por ejecución, y en máquinas que tienen llamadas del sistema
operativo relativamente lentas para obtener la hora del día.
Para mostrar el plan de una consulta simple en una tabla con una sola
columna de tipo integer y 10000 filas:
EXPLAIN SELECT * FROM foo;
QUERY PLAN
---------------------------------------------------------
Seq Scan on foo (cost=0.00..155.00 rows=10000 width=4)
(1 row)
Aquí está la misma consulta, con formato de salida JSON:
EXPLAIN (FORMAT JSON) SELECT * FROM foo;
QUERY PLAN
--------------------------------
[ +
{ +
"Plan": { +
"Node Type": "Seq Scan",+
"Relation Name": "foo", +
"Alias": "foo", +
"Startup Cost": 0.00, +
"Total Cost": 155.00, +
"Plan Rows": 10000, +
"Plan Width": 4 +
} +
} +
]
(1 row)
Si hay un índice y usamos una consulta con una condición
WHERE indexable, EXPLAIN
podría mostrar un plan diferente:
EXPLAIN SELECT * FROM foo WHERE i = 4;
QUERY PLAN
--------------------------------------------------------------
Index Scan using fi on foo (cost=0.00..5.98 rows=1 width=4)
Index Cond: (i = 4)
(2 rows)
Aquí está la misma consulta, pero en formato YAML:
EXPLAIN (FORMAT YAML) SELECT * FROM foo WHERE i='4';
QUERY PLAN
-------------------------------
- Plan: +
Node Type: "Index Scan" +
Scan Direction: "Forward"+
Index Name: "fi" +
Relation Name: "foo" +
Alias: "foo" +
Startup Cost: 0.00 +
Total Cost: 5.98 +
Plan Rows: 1 +
Plan Width: 4 +
Index Cond: "(i = 4)"
(1 row)
El formato XML se deja como ejercicio para el lector.
Aquí está el mismo plan con las estimaciones de costo suprimidas:
EXPLAIN (COSTS FALSE) SELECT * FROM foo WHERE i = 4;
QUERY PLAN
----------------------------
Index Scan using fi on foo
Index Cond: (i = 4)
(2 rows)
Aquí está un ejemplo de un plan de consulta para una consulta que utiliza una función de agregación:
EXPLAIN SELECT sum(i) FROM foo WHERE i < 10;
QUERY PLAN
---------------------------------------------------------------------
Aggregate (cost=23.93..23.93 rows=1 width=4)
-> Index Scan using fi on foo (cost=0.00..23.92 rows=6 width=4)
Index Cond: (i < 10)
(3 rows)
Aquí está un ejemplo del uso de EXPLAIN EXECUTE para
mostrar el plan de ejecución de una consulta preparada:
PREPARE query(int, int) AS SELECT sum(bar) FROM test
WHERE id > $1 AND id < $2
GROUP BY foo;
EXPLAIN ANALYZE EXECUTE query(100, 200);
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=10.77..10.87 rows=10 width=12) (actual time=0.043..0.044 rows=10.00 loops=1)
Group Key: foo
Batches: 1 Memory Usage: 24kB
Buffers: shared hit=4
-> Index Scan using test_pkey on test (cost=0.29..10.27 rows=99 width=8) (actual time=0.009..0.025 rows=99.00 loops=1)
Index Cond: ((id > 100) AND (id < 200))
Index Searches: 1
Buffers: shared hit=4
Planning Time: 0.244 ms
Execution Time: 0.073 ms
(10 rows)
Por supuesto, los números específicos que se muestran aquí dependen del contenido
real de las tablas involucradas. También ten en cuenta que los números, e
incluso la estrategia de consulta seleccionada, pueden variar entre las versiones de
PostgreSQL debido a las mejoras del planificador.
Además, el comando ANALYZE utiliza un muestreo
aleatorio para estimar las estadísticas de los datos; por lo tanto, es
posible que las estimaciones de costo cambien después de una nueva ejecución de
ANALYZE, incluso si la distribución real de los datos
en la tabla no ha cambiado.
Ten en cuenta que el ejemplo anterior mostró un plan “personalizado” (custom plan)
para los valores de parámetros específicos dados en EXECUTE.
También podríamos desear ver el plan genérico para una consulta parametrizada,
lo cual se puede hacer con GENERIC_PLAN:
EXPLAIN (GENERIC_PLAN)
SELECT sum(bar) FROM test
WHERE id > $1 AND id < $2
GROUP BY foo;
QUERY PLAN
-------------------------------------------------------------------------------
HashAggregate (cost=26.79..26.89 rows=10 width=12)
Group Key: foo
-> Index Scan using test_pkey on test (cost=0.29..24.29 rows=500 width=8)
Index Cond: ((id > $1) AND (id < $2))
(4 rows)
En este caso, el analizador (parser) infirió correctamente que $1
y $2 debían tener el mismo tipo de datos
que id, por lo que la falta de información sobre el tipo de parámetro
de PREPARE no fue un problema. En otros casos,
puede ser necesario especificar explícitamente los tipos para los símbolos de los parámetros,
lo cual se puede hacer mediante una conversión de tipo (casting), por ejemplo:
EXPLAIN (GENERIC_PLAN)
SELECT sum(bar) FROM test
WHERE id > $1::integer AND id < $2::integer
GROUP BY foo;
No hay ninguna sentencia EXPLAIN definida en el estándar SQL.
La siguiente sintaxis se utilizaba antes de la versión 9.0 de PostgreSQL y todavía se admite:
EXPLAIN [ ANALYZE ] [ VERBOSE ] sentencia
Ten en cuenta que en esta sintaxis, las opciones deben especificarse exactamente en el orden mostrado.