Usando todas las características de los índices en PostgreSQL


En el mundo de Postgres, los índices son cruciales para navegar un repositorio de base de datos de manera eficiente (llamado montón, montón). Postgres no admite la agrupación en clúster, y la arquitectura MVCC hace que acumule muchas versiones de la misma tupla. Por lo tanto, es muy importante poder crear y mantener índices efectivos para admitir aplicaciones.

Aquí hay algunos consejos para optimizar y mejorar el uso de índices.

Nota: las consultas que se muestran a continuación funcionan en una base de datos de muestra de pagila no modificada.

Usar índices de cobertura


Revisemos la solicitud para recuperar direcciones de correo electrónico para usuarios inactivos. La tabla de customer tiene una columna active , y la solicitud es simple:

 pagila=# EXPLAIN SELECT email FROM customer WHERE active=0; QUERY PLAN ----------------------------------------------------------- Seq Scan on customer (cost=0.00..16.49 rows=15 width=32) Filter: (active = 0) (2 rows) 

La consulta invoca la secuencia completa de escaneo de la tabla del customer . Creemos un índice para la columna active :

 pagila=# CREATE INDEX idx_cust1 ON customer(active); CREATE INDEX pagila=# EXPLAIN SELECT email FROM customer WHERE active=0; QUERY PLAN ----------------------------------------------------------------------------- Index Scan using idx_cust1 on customer (cost=0.28..12.29 rows=15 width=32) Index Cond: (active = 0) (2 rows) 

Ayudó, la exploración posterior se convirtió en una " index scan ". Esto significa que Postgres escaneará el índice idx_cust1 y luego continuará buscando en el montón de la tabla para leer los valores de las otras columnas (en este caso, la columna de email ) que necesita la consulta.

PostgreSQL 11 introdujo índices de cobertura. Le permiten incluir una o más columnas adicionales en el propio índice; sus valores se almacenan en el almacén de datos del índice.

Si utilizamos esta función y agregamos un valor de correo electrónico dentro del índice, Postgres no necesitaría buscar el valor de email en el montón de la tabla. Veamos si esto funciona:

 pagila=# CREATE INDEX idx_cust2 ON customer(active) INCLUDE (email); CREATE INDEX pagila=# EXPLAIN SELECT email FROM customer WHERE active=0; QUERY PLAN ---------------------------------------------------------------------------------- Index Only Scan using idx_cust2 on customer (cost=0.28..12.29 rows=15 width=32) Index Cond: (active = 0) (2 rows) 

" Index Only Scan " nos dice que la consulta ahora solo necesita un índice, lo que ayuda a evitar que todas las E / S de disco lean el montón de tabla.

Hoy en día, los índices de cobertura solo están disponibles para los árboles B. Sin embargo, en este caso, los esfuerzos de escolta serán mayores.

Usando índices parciales


Los índices parciales solo indexan un subconjunto de las filas de una tabla. Esto ahorra el tamaño de los índices y escaneos más rápidos.

Supongamos que necesitamos obtener una lista de direcciones de correo electrónico de nuestros clientes de California. La solicitud será así:

 SELECT c.email FROM customer c JOIN address a ON c.address_id = a.address_id WHERE a.district = 'California'; which has a query plan that involves scanning both the tables that are joined: pagila=# EXPLAIN SELECT c.email FROM customer c pagila-# JOIN address a ON c.address_id = a.address_id pagila-# WHERE a.district = 'California'; QUERY PLAN ---------------------------------------------------------------------- Hash Join (cost=15.65..32.22 rows=9 width=32) Hash Cond: (c.address_id = a.address_id) -> Seq Scan on customer c (cost=0.00..14.99 rows=599 width=34) -> Hash (cost=15.54..15.54 rows=9 width=4) -> Seq Scan on address a (cost=0.00..15.54 rows=9 width=4) Filter: (district = 'California'::text) (6 rows) 

Qué índices ordinarios nos darán:

 pagila=# CREATE INDEX idx_address1 ON address(district); CREATE INDEX pagila=# EXPLAIN SELECT c.email FROM customer c pagila-# JOIN address a ON c.address_id = a.address_id pagila-# WHERE a.district = 'California'; QUERY PLAN --------------------------------------------------------------------------------------- Hash Join (cost=12.98..29.55 rows=9 width=32) Hash Cond: (c.address_id = a.address_id) -> Seq Scan on customer c (cost=0.00..14.99 rows=599 width=34) -> Hash (cost=12.87..12.87 rows=9 width=4) -> Bitmap Heap Scan on address a (cost=4.34..12.87 rows=9 width=4) Recheck Cond: (district = 'California'::text) -> Bitmap Index Scan on idx_address1 (cost=0.00..4.34 rows=9 width=0) Index Cond: (district = 'California'::text) (8 rows) 

El escaneo de la address fue reemplazado por el idx_address1 índice idx_address1 , y luego se escaneó el montón de address .

Dado que esta es una consulta frecuente y necesita ser optimizada, podemos usar un índice parcial que indexa solo aquellas filas con direcciones en las que la 'California' :

 pagila=# CREATE INDEX idx_address2 ON address(address_id) WHERE district='California'; CREATE INDEX pagila=# EXPLAIN SELECT c.email FROM customer c pagila-# JOIN address a ON c.address_id = a.address_id pagila-# WHERE a.district = 'California'; QUERY PLAN ------------------------------------------------------------------------------------------------ Hash Join (cost=12.38..28.96 rows=9 width=32) Hash Cond: (c.address_id = a.address_id) -> Seq Scan on customer c (cost=0.00..14.99 rows=599 width=34) -> Hash (cost=12.27..12.27 rows=9 width=4) -> Index Only Scan using idx_address2 on address a (cost=0.14..12.27 rows=9 width=4) (5 rows) 

Ahora la solicitud solo lee idx_address2 y no toca la tabla de address .

Uso de índices de valores múltiples


Es posible que algunas columnas que deban indexarse ​​no contengan un tipo de datos escalar. jsonb tipos de jsonb como jsonb , arrays y tsvector contienen valores múltiples o múltiples. Si necesita indexar dichas columnas, generalmente tiene que buscar todos los valores individuales en estas columnas.

Intentemos encontrar los nombres de todas las películas que contienen cortes de tomas fallidas. La tabla de film tiene una columna de texto llamada special_features . Si la película tiene esta "propiedad especial", entonces la columna contiene un elemento en forma de matriz de texto Behind The Scenes . Para buscar todas esas películas, debemos seleccionar todas las filas con "Detrás de escena" para cualquier valor de la matriz special_features :

 SELECT title FROM film WHERE special_features @> '{"Behind The Scenes"}'; 

El operador de contención @> verifica si el lado derecho es un subconjunto del lado izquierdo.

Plan de solicitud:

 pagila=# EXPLAIN SELECT title FROM film pagila-# WHERE special_features @> '{"Behind The Scenes"}'; QUERY PLAN ----------------------------------------------------------------- Seq Scan on film (cost=0.00..67.50 rows=5 width=15) Filter: (special_features @> '{"Behind The Scenes"}'::text[]) (2 rows) 

Que solicita un análisis dinámico completo con un costo de 67.

Veamos si el índice B-tree normal nos ayuda:

 pagila=# CREATE INDEX idx_film1 ON film(special_features); CREATE INDEX pagila=# EXPLAIN SELECT title FROM film pagila-# WHERE special_features @> '{"Behind The Scenes"}'; QUERY PLAN ----------------------------------------------------------------- Seq Scan on film (cost=0.00..67.50 rows=5 width=15) Filter: (special_features @> '{"Behind The Scenes"}'::text[]) (2 rows) 

El índice ni siquiera fue considerado. El índice del árbol B no tiene conocimiento de la existencia de elementos individuales en los valores indexados.

Necesitamos un índice GIN.

 pagila=# CREATE INDEX idx_film2 ON film USING GIN(special_features); CREATE INDEX pagila=# EXPLAIN SELECT title FROM film pagila-# WHERE special_features @> '{"Behind The Scenes"}'; QUERY PLAN --------------------------------------------------------------------------- Bitmap Heap Scan on film (cost=8.04..23.58 rows=5 width=15) Recheck Cond: (special_features @> '{"Behind The Scenes"}'::text[]) -> Bitmap Index Scan on idx_film2 (cost=0.00..8.04 rows=5 width=0) Index Cond: (special_features @> '{"Behind The Scenes"}'::text[]) (4 rows) 

GIN-index admite la comparación de valores individuales con valores compuestos indexados, como resultado, el costo del plan de consulta se reduce en más de la mitad.

Deshágase de los índices duplicados


Los índices se acumulan con el tiempo y, a veces, un índice nuevo puede contener la misma definición que uno de los anteriores. Para obtener definiciones de índices de lectura humana de los índices, puede usar la vista de catálogo pg_indexes . También puede encontrar fácilmente las mismas definiciones:

  SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn FROM pg_indexes GROUP BY defn HAVING count(*) > 1; And here's the result when run on the stock pagila database: pagila=# SELECT array_agg(indexname) AS indexes, replace(indexdef, indexname, '') AS defn pagila-# FROM pg_indexes pagila-# GROUP BY defn pagila-# HAVING count(*) > 1; indexes | defn ------------------------------------------------------------------------+------------------------------------------------------------------ {payment_p2017_01_customer_id_idx,idx_fk_payment_p2017_01_customer_id} | CREATE INDEX ON public.payment_p2017_01 USING btree (customer_id {payment_p2017_02_customer_id_idx,idx_fk_payment_p2017_02_customer_id} | CREATE INDEX ON public.payment_p2017_02 USING btree (customer_id {payment_p2017_03_customer_id_idx,idx_fk_payment_p2017_03_customer_id} | CREATE INDEX ON public.payment_p2017_03 USING btree (customer_id {idx_fk_payment_p2017_04_customer_id,payment_p2017_04_customer_id_idx} | CREATE INDEX ON public.payment_p2017_04 USING btree (customer_id {payment_p2017_05_customer_id_idx,idx_fk_payment_p2017_05_customer_id} | CREATE INDEX ON public.payment_p2017_05 USING btree (customer_id {idx_fk_payment_p2017_06_customer_id,payment_p2017_06_customer_id_idx} | CREATE INDEX ON public.payment_p2017_06 USING btree (customer_id (6 rows) 

Índices de superconjunto


Puede suceder que acumule muchos índices, uno de los cuales indexa un subconjunto de las columnas que indexan otros índices. Esto puede ser deseable o no: un superconjunto solo puede escanear por índice, lo cual es bueno, pero puede ocupar demasiado espacio, o la consulta para la que se pretendía optimizar este superconjunto ya no se usa.

Si necesita automatizar la definición de dichos índices, puede comenzar con pg_index desde la tabla pg_catalog .

Índices no utilizados


A medida que se desarrollan las aplicaciones que usan bases de datos, también lo hacen las consultas que usan. Los índices agregados anteriormente ya no pueden ser utilizados por ninguna consulta. Cada vez que se escanea el índice, lo marca el administrador de estadísticas y, en la pg_stat_user_indexes catálogo del sistema pg_stat_user_indexes puede ver el valor idx_scan , que es un contador acumulativo. El seguimiento de este valor durante un período de tiempo (por ejemplo, un mes) dará una buena idea de qué índices no se usan y se pueden eliminar.

Aquí hay una solicitud para obtener los recuentos de escaneo actuales de todos los índices en el esquema 'public' :

 SELECT relname, indexrelname, idx_scan FROM pg_catalog.pg_stat_user_indexes WHERE schemaname = 'public'; with output like this: pagila=# SELECT relname, indexrelname, idx_scan pagila-# FROM pg_catalog.pg_stat_user_indexes pagila-# WHERE schemaname = 'public' pagila-# LIMIT 10; relname | indexrelname | idx_scan ---------------+--------------------+---------- customer | customer_pkey | 32093 actor | actor_pkey | 5462 address | address_pkey | 660 category | category_pkey | 1000 city | city_pkey | 609 country | country_pkey | 604 film_actor | film_actor_pkey | 0 film_category | film_category_pkey | 0 film | film_pkey | 11043 inventory | inventory_pkey | 16048 (10 rows) 

Vuelva a crear índices con menos bloqueos


A menudo, los índices tienen que recrearse, por ejemplo, cuando están inflados en tamaño, y la recreación puede acelerar el escaneo. Además, los índices pueden estar corruptos. Cambiar los parámetros del índice también puede requerir volver a crearlo.

Habilitar creación de índice paralelo


En PostgreSQL 11, crear un índice B-Tree es competitivo. Para acelerar el proceso de creación, se pueden utilizar varios trabajadores paralelos. Sin embargo, asegúrese de que estos parámetros de configuración estén configurados correctamente:

 SET max_parallel_workers = 32; SET max_parallel_maintenance_workers = 16; 

Los valores predeterminados son demasiado pequeños. Idealmente, estos números deberían aumentarse junto con el número de núcleos de procesador. Lea la documentación para más detalles.

Creación de índice de fondo


Puede crear un índice en segundo plano utilizando el parámetro CONCURRENTLY del comando CREATE INDEX :

 pagila=# CREATE INDEX CONCURRENTLY idx_address1 ON address(district); CREATE INDEX 


Este procedimiento de creación de índice difiere del habitual en que no requiere el bloqueo de la tabla y, por lo tanto, no bloquea las operaciones de escritura. Por otro lado, lleva más tiempo y consume más recursos.

Postgres ofrece muchas opciones flexibles para crear índices y formas de resolver casos particulares, así como formas de administrar la base de datos en caso de crecimiento explosivo de su aplicación. Esperamos que estos consejos lo ayuden a agilizar sus consultas y a que su base de datos esté lista para escalar.

Source: https://habr.com/ru/post/453046/


All Articles