sql >> Base de Datos >  >> RDS >> PostgreSQL

PostgreSQL EXPLAIN:¿Cuáles son los costos de consulta?

Comprender el costo EXPLAIN de Postgres

EXPLAIN es muy útil para comprender el rendimiento de una consulta de Postgres. Devuelve el plan de ejecución generado por el planificador de consultas de PostgreSQL para una declaración dada. El EXPLAIN El comando especifica si las tablas a las que se hace referencia en una declaración se buscarán mediante un escaneo de índice o un escaneo secuencial.

Algunas de las primeras cosas que notará al revisar el resultado de un EXPLAIN comando son las estadísticas de costos, por lo que es natural preguntarse qué significan, cómo se calculan y cómo se utilizan.

En resumen, el planificador de consultas de PostgreSQL estima cuánto tiempo tomará la consulta (en una unidad arbitraria), con un costo inicial y un costo total para cada operación. Más sobre eso más adelante. Cuando tiene múltiples opciones para ejecutar una consulta, utiliza estos costos para elegir la opción más barata y, por lo tanto, con suerte la más rápida.

¿En qué unidad están los costos?

Los costos están en una unidad arbitraria. Un malentendido común es que están en milisegundos o alguna otra unidad de tiempo, pero ese no es el caso.

Las unidades de costo están ancladas (de forma predeterminada) a una sola lectura de página secuencial que cuesta 1,0 unidades (seq_page_cost ). Cada fila procesada agrega 0.01 (cpu_tuple_cost ), y cada lectura de página no secuencial agrega 4.0 (random_page_cost ). Hay muchas más constantes como esta, todas las cuales son configurables. Este último es un candidato particularmente común, al menos en el hardware moderno. Lo analizaremos más en un momento.

Costos de inicio

Los primeros números que ve después de cost= se conocen como el "costo de puesta en marcha". Esta es una estimación de cuánto tiempo llevará obtener la primera fila . Como tal, el costo inicial de una operación incluye el costo de sus hijos.

Para un escaneo secuencial, el costo de inicio generalmente será cercano a cero, ya que puede comenzar a obtener filas de inmediato. Para una operación de ordenación, será mayor porque una gran parte del trabajo debe realizarse antes de que las filas puedan comenzar a devolverse.

Para ver un ejemplo, creemos una tabla de prueba simple con 1000 nombres de usuario:

CREATE TABLE users (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username text NOT NULL);
INSERT INTO users (username)
SELECT 'person' || n
FROM generate_series(1, 1000) AS n;
ANALYZE users;

Echemos un vistazo a un plan de consulta simple, con un par de operaciones:

EXPLAIN SELECT * FROM users ORDER BY username;

QUERY PLAN                                                    |
--------------------------------------------------------------+
Sort  (cost=66.83..69.33 rows=1000 width=17)                  |
  Sort Key: username                                          |
  ->  Seq Scan on users  (cost=0.00..17.00 rows=1000 width=17)|

En el plan de consulta anterior, como se esperaba, el costo estimado de ejecución de la instrucción para el Seq Scan es 0.00 , y para el Sort es 66.83 .

Costos totales

La segunda estadística de costos, después del costo inicial y los dos puntos, se conoce como "costo total". Esta es una estimación de cuánto tiempo llevará devolver todas las filas .

Veamos de nuevo ese plan de consulta de ejemplo:

QUERY PLAN                                                    |
--------------------------------------------------------------+
Sort  (cost=66.83..69.33 rows=1000 width=17)                  |
  Sort Key: username                                          |
  ->  Seq Scan on users  (cost=0.00..17.00 rows=1000 width=17)|

Podemos ver que el costo total del Seq Scan la operación es 17.00 . Para el Sort operación es 69.33, que no es mucho más que su costo inicial (como se esperaba).

Los costos totales suelen incluir el costo de las operaciones que los preceden. Por ejemplo, el costo total de la operación Sort anterior incluye el de Seq Scan.

Una excepción importante es LIMIT cláusulas, que el planificador usa para estimar si puede abortar antes de tiempo. Si solo necesita una pequeña cantidad de filas, cuyas condiciones son comunes, puede calcular que una opción de escaneo más simple es más económica (probablemente más rápida).

Por ejemplo:

EXPLAIN SELECT * FROM users LIMIT 1;

QUERY PLAN                                                    |
--------------------------------------------------------------+
Limit  (cost=0.00..0.02 rows=1 width=17)                      |
  ->  Seq Scan on users  (cost=0.00..17.00 rows=1000 width=17)|

Como puede ver, el costo total notificado en el nodo Seq Scan sigue siendo 17,00, pero el costo total de la operación Límite es 0,02. Esto se debe a que el planificador espera que solo tenga que procesar 1 fila de 1000, por lo que el costo, en este caso, se estima en 1000 del total.

Cómo se calculan los costos

Para calcular estos costes, el planificador de consultas de Postgres utiliza tanto constantes (algunas de las cuales ya hemos visto) como metadatos sobre el contenido de la base de datos. Los metadatos a menudo se denominan "estadísticas".

Las estadísticas se recopilan a través de ANALYZE (no debe confundirse con EXPLAIN parámetro del mismo nombre), y almacenado en pg_statistic . También se actualizan automáticamente como parte del vacío automático.

Estas estadísticas incluyen una serie de cosas muy útiles, como la cantidad aproximada de filas que tiene cada tabla y cuáles son los valores más comunes en cada columna.

Veamos un ejemplo simple, usando los mismos datos de consulta que antes:

EXPLAIN SELECT count(*) FROM users;

QUERY PLAN                                                   |
-------------------------------------------------------------+
Aggregate  (cost=19.50..19.51 rows=1 width=8)                |
  ->  Seq Scan on users  (cost=0.00..17.00 rows=1000 width=0)|

En nuestro caso, las estadísticas del planificador sugirieron que los datos de la tabla se almacenaron en 7 páginas (o bloques) y que se devolverían 1000 filas. Los parámetros de costo seq_page_cost , cpu_tuple_cost y cpu_operator_cost se dejaron en sus valores predeterminados de 1 , 0.01 y 0.0025 respectivamente.

Como tal, el costo total de Seq Scan se calculó como:

Total cost of Seq Scan
= (estimated sequential page reads * seq_page_cost) + (estimated rows returned * cpu_tuple_cost)
= (7 * 1) + (1000 * 0.01)
= 7 + 10.00
= 17.00

Y para el Agregado como:

Total cost of Aggregate
= (cost of Seq Scan) + (estimated rows processed * cpu_operator_cost) + (estimated rows returned * cpu_tuple_cost)
= (17.00) + (1000 * 0.0025) + (1 * 0.01) 
= 17.00 + 2.50 + 0.01
= 19.51 

Cómo utiliza el planificador los costes

Como sabemos que Postgres elegirá el plan de consulta con el costo total más bajo, podemos usarlo para tratar de comprender las elecciones que ha hecho. Por ejemplo, si una consulta no usa el índice que espera, puede usar configuraciones como enable_seqscan para desalentar masivamente ciertas opciones de planes de consulta. En este punto, ¡no debería sorprenderse al escuchar que configuraciones como esta funcionan aumentando los costos!
Los números de fila son una parte extremadamente importante de la estimación de costos. Se utilizan para calcular estimaciones para diferentes órdenes de unión, algoritmos de unión, tipos de escaneo y más. Las estimaciones de costos de fila que se desvían mucho pueden llevar a que la estimación de costos se desvíe mucho, lo que en última instancia puede resultar en una elección de plan subóptima.

Uso de EXPLAIN ANALYZE para obtener un plan de consulta

Cuando escribe sentencias SQL en PostgreSQL, el ANALYZE El comando es clave para optimizar las consultas, haciéndolas más rápidas y eficientes. Además de mostrar el plan de consulta y las estimaciones de PostgreSQL, EXPLAIN ANALYZE realiza la consulta (cuidado con UPDATE y DELETE !) y muestra el tiempo de ejecución real y el número de filas para cada paso del proceso de ejecución. Esto es necesario para monitorear el rendimiento de SQL.

Puedes usar EXPLAIN ANALYZE para comparar el número estimado de filas con las filas reales devueltas por cada operación.

Veamos un ejemplo, usando los mismos datos nuevamente:

QUERY PLAN                                                                                                 |
-----------------------------------------------------------------------------------------------------------+
Sort  (cost=66.83..69.33 rows=1000 width=17) (actual time=20.569..20.684 rows=1000 loops=1)                |
  Sort Key: username                                                                                       |
  Sort Method: quicksort  Memory: 102kB                                                                    |
  ->  Seq Scan on users  (cost=0.00..17.00 rows=1000 width=17) (actual time=0.048..0.596 rows=1000 loops=1)|
Planning Time: 0.171 ms                                                                                    |
Execution Time: 20.793 ms                                                                                  |

Podemos ver que el costo total de ejecución sigue siendo 69,33, siendo la mayor parte de la operación Ordenar, y 17,00 provenientes de la exploración secuencial. Tenga en cuenta que el tiempo de ejecución de la consulta es un poco menos de 21 ms.

Escaneo secuencial frente a escaneo de índice

Ahora, agreguemos un índice para tratar de evitar ese tipo costoso de toda la tabla:

​​CREATE INDEX people_username_idx ON users (username);

EXPLAIN ANALYZE SELECT * FROM users ORDER BY username;

QUERY PLAN                                                                                                                       |
---------------------------------------------------------------------------------------------------------------------------------+
Index Scan using people_username_idx on users  (cost=0.28..28.27 rows=1000 width=17) (actual time=0.052..1.494 rows=1000 loops=1)|
Planning Time: 0.186 ms                                                                                                          |
Execution Time: 1.686 ms                                                                                                         |

Como puede ver, el planificador de consultas ahora ha elegido un Index Scan, ya que el costo total de ese plan es 28,27 (inferior a 69,33). Parece que el escaneo de índice fue más eficiente que el escaneo secuencial, ya que el tiempo de ejecución de la consulta ahora es de poco menos de 2 ms.

Ayudar al planificador a estimar con mayor precisión

Podemos ayudar al planificador a estimar con mayor precisión de dos maneras:

  1. Ayúdalo a recopilar mejores estadísticas
  2. Ajuste las constantes que usa para los cálculos

Las estadísticas pueden ser especialmente malas después de un gran cambio en los datos de una tabla. Como tal, al cargar una gran cantidad de datos en una tabla, puede ayudar a Postgres ejecutando un ANALYZE manual. en eso. Las estadísticas tampoco persisten después de una actualización de versión principal, por lo que es otro momento importante para hacer esto.

Naturalmente, las tablas también cambian con el tiempo, por lo que puede ser muy útil ajustar la configuración de vacío automático para asegurarse de que se ejecute con la frecuencia suficiente para su carga de trabajo.

Si tiene problemas con estimaciones incorrectas para una columna con una distribución sesgada, puede beneficiarse al aumentar la cantidad de información que recopila Postgres mediante el uso de ALTER TABLE SET STATISTICS comando, o incluso el default_statistics_target para toda la base de datos.

Otra causa común de malas estimaciones es que, por defecto, Postgres supondrá que dos columnas son independientes. Puede solucionar esto pidiéndole que recopile datos de correlación en dos columnas de la misma tabla a través de estadísticas extendidas.

En el frente de ajuste constante, hay muchos parámetros que puede ajustar para adaptarse a su hardware. Suponiendo que está utilizando SSD, es probable que, como mínimo, desee ajustar su configuración de random_page_cost . El valor predeterminado es 4, que es 4 veces más caro que el seq_page_cost miramos antes. Esta proporción tenía sentido en discos giratorios, pero en SSD tiende a penalizar demasiado la E/S aleatoria. Como tal, una configuración más cercana a 1, o entre 1 y 2, podría tener más sentido. En ScaleGrid, el valor predeterminado es 1.

¿Puedo eliminar los costos de los planes de consulta?

Por muchas de las razones mencionadas anteriormente, la mayoría de las personas dejan los costos activados cuando ejecutan EXPLAIN . Sin embargo, si lo desea, puede desactivarlos utilizando los COSTS parámetro.

EXPLAIN (COSTS OFF) SELECT * FROM users LIMIT 1;

QUERY PLAN             |
-----------------------+
Limit                  |
  ->  Seq Scan on users|

Conclusión

Para resumir, los costos en los planes de consulta son estimaciones de Postgres sobre cuánto tiempo llevará una consulta SQL, en una unidad arbitraria.

Elige el plan con el costo total más bajo, según algunas constantes configurables y algunas estadísticas que ha recopilado.

Ayudarlo a estimar estos costos con mayor precisión es muy importante para ayudarlo a tomar buenas decisiones y mantener el rendimiento de sus consultas.

¿Interesado en aprender más sobre ScaleGrid?

Para obtener más información sobre cómo ScaleGrid puede ayudarlo a administrar sus bases de datos, comuníquese con nosotros y le mostraremos todo lo que nuestro DBaaS tiene para ofrecer. Obtenga más información sobre cómo ScaleGrid puede permitirle concentrarse más en desarrollar su producto y menos en administrar bases de datos.