Usando todos os recursos de índices no PostgreSQL


No mundo do Postgres, os índices são cruciais para navegar com eficiência em um repositório de banco de dados (chamado heap, heap). O Postgres não suporta clustering e a arquitetura MVCC faz com que você acumule muitas versões da mesma tupla. Portanto, é muito importante poder criar e manter índices eficazes para dar suporte a aplicativos.

Aqui estão algumas dicas para otimizar e melhorar o uso de índices.

Nota: as consultas mostradas abaixo funcionam em um banco de dados de amostra de pagila não modificado.

Usando índices de cobertura


Vamos revisar a solicitação para recuperar endereços de email para usuários inativos. A tabela do customer possui uma coluna active e a solicitação é simples:

 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) 

A consulta chama a sequência completa da verificação da tabela do customer . Vamos criar um índice para a coluna 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) 

Ajudou, a verificação subsequente se transformou em uma " index scan ". Isso significa que o Postgres varrerá o índice idx_cust1 e continuará pesquisando a pilha da tabela para ler os valores das outras colunas (neste caso, a coluna de email ) necessárias à consulta.

O PostgreSQL 11 introduziu índices de cobertura. Eles permitem incluir uma ou mais colunas adicionais no próprio índice - seus valores são armazenados no armazenamento de dados do índice.

Se usássemos esse recurso e adicionássemos um valor de email ao índice, o Postgres não precisaria procurar o valor do email no heap da tabela. Vamos ver se isso 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) 

A " Index Only Scan " nos diz que a consulta agora precisa de apenas um índice, o que ajuda a evitar todas as E / S de disco para ler a pilha da tabela.

Hoje, os índices de cobertura estão disponíveis apenas para árvores B. No entanto, nesse caso, os esforços de escolta serão maiores.

Usando índices parciais


Os índices parciais indexam apenas um subconjunto das linhas em uma tabela. Isso economiza o tamanho dos índices e verificações mais rápidas.

Suponha que precisamos obter uma lista de endereços de e-mail de nossos clientes da Califórnia. A solicitação será assim:

 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) 

Quais índices comuns nos fornecerão:

 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) 

A verificação de address foi substituída pela idx_address1 índice idx_address1 e, em seguida, o heap de address foi verificado.

Como essa é uma consulta frequente e precisa ser otimizada, podemos usar um índice parcial que indexa apenas as linhas com endereços nos quais a '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) 

Agora, a solicitação lê apenas idx_address2 e não toca na tabela de address .

Usando índices com vários valores


Algumas colunas que precisam ser indexadas podem não conter um tipo de dados escalar. Tipos de jsonb como jsonb , arrays e tsvector contêm valores múltiplos ou múltiplos. Se você precisar indexar essas colunas, geralmente precisará procurar todos os valores individuais nessas colunas.

Vamos tentar encontrar os nomes de todos os filmes que contêm cortes de tomadas sem êxito. A tabela de film possui uma coluna de texto chamada special_features . Se o filme tiver essa "propriedade especial", a coluna conterá um elemento na forma de uma matriz de texto nos Behind The Scenes . Para procurar por todos esses filmes, precisamos selecionar todas as linhas com "Bastidores" para quaisquer valores da matriz special_features :

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

O operador de contenção @> verifica se o lado direito é um subconjunto do lado esquerdo.

Plano de Solicitação:

 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) 

O que solicita uma verificação completa da pilha com um custo de 67.

Vamos ver se o índice B-tree comum nos ajuda a:

 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) 

O índice nem sequer foi considerado. O índice da árvore B não está ciente da existência de elementos individuais nos valores indexados.

Precisamos de um í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) 

O índice GIN suporta a comparação de valores individuais com valores compostos indexados, como resultado, o custo do plano de consulta é reduzido em mais da metade.

Livre-se de índices duplicados


Os índices se acumulam com o tempo e, às vezes, um novo índice pode conter a mesma definição que uma das anteriores. Para obter definições SQL legíveis humanas de índices, você pode usar a exibição de catálogo pg_indexes . Você também pode encontrar facilmente as mesmas definições:

  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


Pode acontecer que você acumule muitos índices, um dos quais indexa um subconjunto das colunas que indexam outros índices. Isso pode ser desejável ou não - um superconjunto só pode varrer por índice, o que é bom, mas pode ocupar muito espaço ou a consulta para a qual esse superconjunto deveria ser otimizado não é mais usada.

Se você precisar automatizar a definição desses índices, poderá começar com pg_index na tabela pg_catalog .

Índices não utilizados


À medida que os aplicativos que usam bancos de dados se desenvolvem, o mesmo ocorre com as consultas que eles usam. Os índices adicionados anteriormente não podem mais ser usados ​​por nenhuma consulta. Cada vez que o índice é varrido, é marcado pelo gerenciador de estatísticas e, na pg_stat_user_indexes catálogo do sistema pg_stat_user_indexes é possível ver o valor idx_scan , que é um contador cumulativo. O rastreamento desse valor por um período de tempo (digamos, um mês) dará uma boa idéia de quais índices não são usados ​​e podem ser excluídos.

Aqui está uma solicitação para obter as contagens atuais de varredura de todos os índices no 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) 

Recrie índices com menos bloqueios


Muitas vezes, os índices precisam ser recriados, por exemplo, quando são inflados em tamanho, e a recriação pode acelerar a verificação. Além disso, os índices podem estar corrompidos. Alterar os parâmetros do índice também pode exigir a sua recriação.

Ativar criação de índice paralelo


No PostgreSQL 11, a criação de um índice B-Tree é competitiva. Para acelerar o processo de criação, vários trabalhadores paralelos podem ser usados. No entanto, verifique se esses parâmetros de configuração estão definidos corretamente:

 SET max_parallel_workers = 32; SET max_parallel_maintenance_workers = 16; 

Os valores padrão são muito pequenos. Idealmente, esses números devem ser aumentados junto com o número de núcleos do processador. Leia a documentação para mais detalhes.

Criação de índice em segundo plano


Você pode criar um índice em segundo plano usando o parâmetro CONCURRENTLY do comando CREATE INDEX :

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


Esse procedimento de criação de índice difere do usual, pois não requer bloqueio de tabela e, portanto, não bloqueia operações de gravação. Por outro lado, leva mais tempo e consome mais recursos.

O Postgres oferece muitas opções flexíveis para a criação de índices e maneiras de resolver casos específicos, além de maneiras de gerenciar o banco de dados em caso de crescimento explosivo do seu aplicativo. Esperamos que essas dicas o ajudem a tornar suas consultas rápidas e o banco de dados pronto para escalar.

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


All Articles