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

Notas sobre los índices B-Tree de PostgreSQL

PostgreSQL viene con no menos de 6 tipos diferentes de índices, siendo el B-Treeindex el más utilizado. Siga leyendo para obtener más información sobre B-Treeindexes en PostgreSQL.

Tipos de índices

Un índice en PostgreSQL, como los creados para PRIMARY KEY y UNIQUE en una sentencia CREATE TABLE o creados explícitamente con una sentencia CREATE INDEX, son de un "tipo" particular (aunque técnicamente deberíamos llamarlos "métodos de acceso a índices").

PostgreSQL viene con estos tipos de índice integrados:

  • Árbol B
  • hachís
  • GIN:índice invertido generalizado
  • BRIN:índice de rango de bloque (solo en v9.5 y superior)
  • GiST:árbol de búsqueda invertida generalizada
  • SP-GiST:GiST con particiones espaciales

B-Tree es el tipo de índice predeterminado y más utilizado. La especificación de una clave principal o única dentro de una declaración CREATE TABLE hace que PostgreSQL cree índices B-Tree. Las declaraciones CREATE INDEX sin la cláusula USING también crearán índices B-Tree:

-- the default index type is btree
CREATE INDEX ix_year ON movies (year);

-- equivalent, explicitly lists the index type
CREATE INDEX ix_year ON movies USING btree (year);

Pedir

Los índices B-Tree están inherentemente ordenados. PostgreSQL puede hacer uso de este orden en lugar de ordenar la expresión indexada. Por ejemplo, ordenar los títulos de todas las películas de los 80 por título requeriría ordenar:

idxdemo=# explain select title from movies where year between 1980 and 1989 order by title asc;
                                    QUERY PLAN
----------------------------------------------------------------------------------
 Sort  (cost=240.79..245.93 rows=2056 width=17)
   Sort Key: title
   ->  Index Scan using ix_year on movies  (cost=0.29..127.65 rows=2056 width=17)
         Index Cond: ((year >= 1980) AND (year <= 1989))
(4 rows)

Pero si los está clasificando por la columna indexada (año), no se requiere una clasificación adicional.

idxdemo=# explain select title from movies where year between 1980 and 1989 order by year asc;
                                 QUERY PLAN
----------------------------------------------------------------------------
 Index Scan using ix_year on movies  (cost=0.29..127.65 rows=2056 width=21)
   Index Cond: ((year >= 1980) AND (year <= 1989))
(2 rows)

Factor de relleno

Para las tablas que no se van a actualizar, puede aumentar el "factor de relleno" desde el valor predeterminado de 90, lo que debería generar índices ligeramente más pequeños y más rápidos. Por el contrario, si hay actualizaciones frecuentes de la tabla que involucren el parámetro indexado, puede reducir el factor de relleno a un número más pequeño; esto permitirá inserciones y actualizaciones más rápidas, a costa de índices ligeramente más grandes.

CREATE INDEX ix_smd ON silent_movies (director) WITH (fillfactor = 100);

Indización en texto

Los índices B-Tree pueden ayudar en la coincidencia de prefijos de texto. Hagamos una consulta para enumerar todas las películas que comienzan con la letra 'T':

idxdemo=> explain select title from movies where title like 'T%';
                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on movies  (cost=0.00..1106.94 rows=8405 width=17)
   Filter: (title ~~ 'T%'::text)
(2 rows)

Este plan requiere un escaneo secuencial completo de la tabla. ¿Qué sucede si agregamos un índice B-Tree en movies.title?

idxdemo=> create index ix_title on movies (title);
CREATE INDEX

idxdemo=> explain select title from movies where title like 'T%';
                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on movies  (cost=0.00..1106.94 rows=8405 width=17)
   Filter: (title ~~ 'T%'::text)
(2 rows)

Bueno, eso no ayudó en absoluto. Sin embargo, hay una especie de polvo mágico que podemos rociar para que Postgres haga lo que queremos:

idxdemo=> create index ix_title2 on movies (title text_pattern_ops);
CREATE INDEX

idxdemo=> explain select title from movies where title like 'T%';
                                 QUERY PLAN
-----------------------------------------------------------------------------
 Bitmap Heap Scan on movies  (cost=236.08..1085.19 rows=8405 width=17)
   Filter: (title ~~ 'T%'::text)
   ->  Bitmap Index Scan on ix_title2  (cost=0.00..233.98 rows=8169 width=0)
         Index Cond: ((title ~>=~ 'T'::text) AND (title ~<~ 'U'::text))
(4 rows)

El plan ahora usa un índice y el costo se ha reducido. La magia aquí es "text_pattern_ops" que permite que el índice B-Tree sobre una expresión de "texto" se use para operadores de patrones (LIKE y expresiones regulares). El “text_pattern_ops” se llama OperatorClass.

Tenga en cuenta que esto funcionará solo para patrones con un prefijo de texto fijo, por lo que "%Enojado%" o "%Hombres" no funcionarán. Use la búsqueda de texto completo de PostgreSQL para consultas de texto avanzadas.

Índices de cobertura

Los índices de cobertura se agregaron a PostgreSQL en v11. Los índices de cobertura le permiten incluir el valor de una o más expresiones junto con la expresión indexada dentro del índice.

Intentemos consultar todos los títulos de películas, ordenados por año de lanzamiento:

idxdemo=# explain select title from movies order by year asc;
                             QUERY PLAN
--------------------------------------------------------------------
 Sort  (cost=3167.73..3239.72 rows=28795 width=21)
   Sort Key: year
   ->  Seq Scan on movies  (cost=0.00..1034.95 rows=28795 width=21)
(3 rows)

Esto implica un escaneo secuencial completo de la tabla, seguido de una especie de columnas proyectadas. Primero agreguemos un índice regular en movies.year:

idxdemo=# create index ix_year on movies (year);
CREATE INDEX

idxdemo=# explain select title from movies order by year asc;
                                  QUERY PLAN
------------------------------------------------------------------------------
 Index Scan using ix_year on movies  (cost=0.29..1510.22 rows=28795 width=21)
(1 row)

Ahora Postgres decide usar el índice para sacar las entradas de la tabla directamente en el orden deseado. Es necesario consultar la tabla porque el índice contiene solo el valor de 'año' y la referencia a la tupla en la tabla.

Si incluimos el valor de 'título' también dentro del índice, la búsqueda en la tabla se puede evitar por completo. Usemos la nueva sintaxis para crear dicho índice:

idxdemo=# create index ix_year_cov on movies (year) include (title);
CREATE INDEX
Time: 92.618 ms

idxdemo=# drop index ix_year;
DROP INDEX

idxdemo=# explain select title from movies order by year asc;
                                      QUERY PLAN
---------------------------------------------------------------------------------------
 Index Only Scan using ix_year_cov on movies  (cost=0.29..2751.59 rows=28795 width=21)
(1 row)

Postgres ahora está usando Index OnlyScan, lo que significa que la búsqueda de tablas se evita por completo. Tenga en cuenta que tuvimos que descartar el índice anterior porque Postgres no eligió ix_year_cov sobre ix_year para esta consulta.

Agrupación

Desafortunadamente, PostgreSQL no admite el ordenamiento físico automático de filas en una tabla, a diferencia de los "índices agrupados" en otros RDBMS. Si la mayoría de sus consultas van a extraer la mayoría de las filas de una tabla mayormente estática en un orden fijo, sería una buena idea diseñar el almacenamiento de la tabla física en ese orden y usar exploraciones secuenciales. Para reordenar una tabla físicamente en el orden dictado por un índice, use:

CLUSTER VERBOSE movies USING ix_year;

Por lo general, usaría un índice B-Tree para reagrupar una tabla, ya que proporciona un orden completo para todas las filas de la tabla.

Estadísticas del índice

¿Cuánto espacio en disco ocupa su índice? La función pg_relation_size puede responder a eso:

idxdemo=# select * from pg_relation_size('ix_year');
 pg_relation_size
------------------
           663552
(1 row)

Esto devuelve el espacio en disco utilizado por el índice, en bytes.

Se puede recopilar más información sobre el índice utilizando la extensión estándar pgstattuple. Antes de usar las funciones a continuación, debe hacer CREATE EXTENSION pgstattuple; en la base de datos relevante como superusuario. El uso de estas funciones también requiere privilegios de superusuario.

El pgstattuple devuelve, entre otras cosas, el espacio no utilizado (free_space ) y reutilizable (dead_tuple_len ) espacio en disco dentro del índice. Esto puede ser muy útil para decidir si ejecutar un REINDEX para reducir la hinchazón del índice.

idxdemo=# select * from pgstattuple('ix_year'::regclass);
-[ RECORD 1 ]------+-------
table_len          | 663552
tuple_count        | 28795
tuple_len          | 460720
tuple_percent      | 69.43
dead_tuple_count   | 0
dead_tuple_len     | 0
dead_tuple_percent | 0
free_space         | 66232
free_percent       | 9.98

El pgstattuple La función devuelve información específica de B-Tree, incluido el nivel del árbol:

idxdemo=# select * from pgstatindex('ix_year'::regclass);
-[ RECORD 1 ]------+-------
version            | 2
tree_level         | 1
index_size         | 663552
root_block_no      | 3
internal_pages     | 1
leaf_pages         | 79
empty_pages        | 0
deleted_pages      | 0
avg_leaf_density   | 89.72
leaf_fragmentation | 0

Esto se puede usar para decidir si ajustar el factor de relleno del índice.

Examinando el contenido del índice B-Tree

Incluso los contenidos del B-Tree se pueden examinar directamente, usando la extensiónpageinspect. El uso de esta extensión necesita privilegios de superusuario.

Estas son las propiedades de una sola página (aquí, la página 13) del índice:

idxdemo=# select * from bt_page_stats('ix_year', 13);
-[ RECORD 1 ]-+-----
blkno         | 13
type          | l
live_items    | 367
dead_items    | 0
avg_item_size | 16
page_size     | 8192
free_size     | 808
btpo_prev     | 12
btpo_next     | 14
btpo          | 0
btpo_flags    | 1

Y aquí están los contenidos reales de cada elemento (limitado a 5 aquí) en la página:

idxdemo=# select * from bt_page_items('ix_year', 13) limit 5;
 itemoffset |   ctid   | itemlen | nulls | vars |          data
------------+----------+---------+-------+------+-------------------------
          1 | (104,40) |      16 | f     | f    | 86 07 00 00 00 00 00 00
          2 | (95,38)  |      16 | f     | f    | 86 07 00 00 00 00 00 00
          3 | (95,39)  |      16 | f     | f    | 86 07 00 00 00 00 00 00
          4 | (95,40)  |      16 | f     | f    | 86 07 00 00 00 00 00 00
          5 | (96,1)   |      16 | f     | f    | 86 07 00 00 00 00 00 00
(5 rows)

Y si está pensando en escribir una consulta para agregar algo sobre cada página, también necesitará el número total de páginas en la relación, que puede generarse a través de pg_relpages del pgstattuple extensión:

idxdemo=# select pg_relpages('ix_year');
 pg_relpages
-------------
          81
(1 row)

Otros tipos de índice

Los índices B-Tree son herramientas versátiles para optimizar las consultas. Con un poco de experimentación y planificación, se puede utilizar para mejorar enormemente los tiempos de respuesta de las aplicaciones y los trabajos de informes.

Los otros tipos de índices de PostgreSQL también son útiles y pueden ser más eficientes y eficaces que B-Tree en casos específicos. Este artículo brinda una descripción general rápida de todos los tipos.

¿Tiene algún consejo sobre los índices que le gustaría compartir? ¡Déjalos como un comentario a continuación!