PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Notas sobre os índices do PostgreSQL B-Tree


O PostgreSQL vem com nada menos que 6 tipos diferentes de índices, sendo o B-Treeindex o mais comumente usado. Continue lendo para saber mais sobre B-Treeindexes no PostgreSQL.

Tipos de índices


Um índice no PostgreSQL, como aqueles criados para PRIMARY KEYs e UNIQUEs em uma instrução CREATE TABLE ou criados explicitamente com uma instrução CREATE INDEX, são de um “tipo” específico (embora tecnicamente devêssemos chamá-los de “métodos indexaccess”).

O PostgreSQL vem com estes tipos de índice integrados:
  • Árvore B
  • Hash
  • GIN – Índice invertido generalizado
  • BRIN – Índice de intervalo de bloqueio (somente na v9.5 e superior)
  • GiST – Árvore de pesquisa invertida generalizada
  • SP-GiST – GiST Particionado por Espaço

B-Tree é o tipo de índice padrão e mais usado. Especificar uma chave primária ou uma única dentro de uma instrução CREATE TABLE faz com que o PostgreSQL crie índices B-Tree. As instruções CREATE INDEX sem a cláusula USING também criarão í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);

Pedido


Os índices B-Tree são inerentemente ordenados. O PostgreSQL pode usar essa ordem em vez de ordenar na expressão indexada. Por exemplo, obter os títulos de todos os filmes dos anos 80 classificados por título exigiria uma classificação:
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)

Mas se você estiver classificando-os pela coluna indexada (ano), a classificação adicional não será necessária.
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)

Fator de preenchimento


Para tabelas que não serão atualizadas, você pode aumentar o “fator de preenchimento” do padrão de 90, o que deve fornecer índices um pouco menores e mais rápidos. Por outro lado, se houver atualizações frequentes da tabela envolvendo o parâmetro indexado, você pode reduzir o fator de preenchimento para um número menor – isso permitirá inserções e atualizações mais rápidas, ao custo de índices um pouco maiores.
CREATE INDEX ix_smd ON silent_movies (director) WITH (fillfactor = 100);

Indexação no texto


Os índices B-Tree podem ajudar na correspondência de prefixo de texto. Vamos fazer uma consulta para listar todos os filmes que começam com a 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)

Esse plano exige uma varredura sequencial completa da tabela. O que acontece se adicionarmos um índice B-Tree em 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)

Bem, isso não ajudou em nada. No entanto, existe uma forma de pó mágico que podemos polvilhar para que o Postgres faça o 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)

O plano agora usa um índice e o custo foi reduzido. A mágica aqui é “text_pattern_ops” que permite que o índice B-Tree sobre uma expressão “texto” seja usado para operadores de padrão (LIKE e expressões regulares). O “text_pattern_ops” é chamado de OperatorClass.

Observe que isso funcionará apenas para padrões com um prefixo de texto fixo, portanto, “%Angry%” ou “%Men” não funcionará. Use a pesquisa de texto completo do PostgreSQL para consultas de texto avançadas.

Cobertura de índices


Índices de cobertura foram adicionados ao PostgreSQL na v11. Cobrir índices permite incluir o valor de uma ou mais expressões junto com a expressão indexada dentro do índice.

Vamos tentar consultar todos os títulos de filmes, ordenados por ano de lançamento:
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)

Isso envolve uma varredura seqüencial completa da tabela, seguida por uma espécie de colunas projetadas. Vamos primeiro adicionar um índice regular em 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)

Agora o Postgres decide usar o índice para extrair as entradas da tabela diretamente na ordem desejada. A tabela precisa ser consultada porque o índice contém apenas o valor de ‘year’ e a referência à tupla na tabela.

Se incluirmos o valor de 'título' também dentro do índice, a consulta à tabela pode ser totalmente evitada. Vamos usar a nova sintaxe para criar esse í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)

O Postgres agora está usando um Index OnlyScan, o que significa que a pesquisa de tabela é totalmente evitada. Observe que tivemos que descartar o índice antigo, porque o Postgres não escolheu ix_year_cov em vez de ix_year para esta consulta.

Agrupamento


PostgreSQL infamemente não suporta ordenação física automática de linhas em uma tabela, ao contrário de “índices clusterizados” em outros RDBMS. Se a maioria de suas consultas for extrair a maioria das linhas de uma tabela principalmente estática em uma ordem fixa, seria uma boa ideia organizar o armazenamento da tabela física nessa ordem e usar varreduras sequenciais. Para reordenar uma tabela fisicamente na ordem ditada por um índice, use:
CLUSTER VERBOSE movies USING ix_year;

Normalmente, você usaria um índice B-Tree para reagrupar uma tabela, pois ele fornece uma ordem completa para todas as linhas da tabela.

Estatísticas do índice


Quanto espaço em disco seu índice ocupa? A função pg_relation_size pode responder a isso:
idxdemo=# select * from pg_relation_size('ix_year');
 pg_relation_size
------------------
           663552
(1 row)

Isso retorna o espaço em disco usado pelo índice, em bytes.

Mais informações sobre o índice podem ser coletadas usando a extensão padrãopgstattuple. Antes de usar as funções abaixo, você precisa fazer um CREATE EXTENSION pgstattuple; no banco de dados relevante como um superusuário. O uso dessas funções também precisa de privilégios de superusuário.

O pgstattuple A função retorna, entre outras coisas, o não utilizado (free_space ) e reutilizável (dead_tuple_len ) espaço em disco dentro do índice. Isso pode ser muito útil para decidir se deve executar um REINDEX para reduzir o inchaço do í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

O pgstattuple A função retorna informações específicas de B-Tree, incluindo o nível da árvore:
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

Isso pode ser usado para decidir se o fator de preenchimento do índice deve ser ajustado.

Examinando o conteúdo do índice B-Tree


Mesmo o conteúdo da B-Tree pode ser examinado diretamente, usando a extensãopageinspect. O uso desta extensão precisa de privilégios de superusuário.

Aqui estão as propriedades de uma única página (aqui, a 13ª página) do í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

E aqui estão os conteúdos reais de cada item (limitado a 5 aqui) na 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)

E se você está pensando em escrever uma consulta para agregar algo em cada página, você também precisará do número total de páginas na relação, que pode ser gerada via pg_relpages do pgstattuple extensão:
idxdemo=# select pg_relpages('ix_year');
 pg_relpages
-------------
          81
(1 row)

Outros tipos de índice


Os índices B-Tree são ferramentas versáteis para otimizar consultas. Com um pouco de experimentação e planejamento, ele pode ser usado para melhorar muito os tempos de resposta de aplicativos e trabalhos de relatório.

Os outros tipos de índice do PostgreSQL também são úteis e podem ser mais eficientes e performáticos que o B-Tree em casos específicos. Este artigo fornece uma visão geral de todos os tipos.

Tem uma dica sobre índices que gostaria de compartilhar? Deixe-os como um comentário abaixo!