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!