Vamos fazer algumas suposições:
Eu tenho uma tabela que se parece com isso:
a | b
---+---
a | -1
a | 17
...
a | 21
c | 17
c | -3
...
c | 22
Fatos sobre o meu conjunto:
O tamanho da tabela inteira é ~ 10 10 linhas.
Eu tenho ~ 100k linhas com valor
a
na colunaa
, semelhante para outros valores (por exemploc
).Isso significa ~ 100k valores distintos na coluna 'a'.
A maioria das minhas consultas lerá todos ou a maioria dos valores para um determinado valor em a, por exemplo
select sum(b) from t where a = 'c'
.A tabela é escrita de tal maneira que os valores consecutivos são fisicamente próximos (ou é escrito em ordem ou assumimos que
CLUSTER
foi usado nessa tabela e colunaa
).A tabela raramente é atualizada, se é que alguma vez foi atualizada, estamos preocupados apenas com a velocidade de leitura.
A tabela é relativamente estreita (digamos, ~ 25 bytes por tupla, + 23 bytes de sobrecarga).
Agora, a pergunta é: que tipo de índice devo usar? Meu entendimento é:
BTree Meu problema aqui é que o índice BTree será enorme, pois, tanto quanto eu sei, ele armazenará valores duplicados (é necessário, pois não pode assumir que a tabela esteja fisicamente classificada). Se o BTree for enorme, acabo tendo que ler o índice e as partes da tabela para as quais o índice aponta. (Podemos usar
fillfactor = 100
para diminuir um pouco o tamanho do índice.)BRIN Meu entendimento é que eu posso ter um pequeno índice aqui à custa da leitura de páginas inúteis. Usar um pequeno
pages_per_range
significa que o índice é maior (o que é um problema com o BRIN, pois preciso ler todo o índice), e ter um grandepages_per_range
significa que vou ler muitas páginas inúteis. Existe uma fórmula mágica para encontrar um bom valorpages_per_range
que leve em consideração essas compensações?GIN / GiST Não tenho certeza de que sejam relevantes aqui, pois são usados principalmente para pesquisa de texto completo, mas também ouvi dizer que eles são bons em lidar com chaves duplicadas. Um índice
GIN
ouGiST
ajudaria aqui?
Outra pergunta é: o Postgres usará o fato de uma tabela ser CLUSTER
editada (supondo que não haja atualizações) no planejador de consultas (por exemplo, pesquisando binário as páginas de início / fim relevantes)? Um pouco relacionado, posso apenas armazenar todas as minhas colunas em um BTree e soltar a tabela completamente (ou obter algo equivalente, acredito que esses são índices agrupados no SQL server)? Existe algum índice híbrido BTree / BRIN que ajudaria aqui?
Prefiro evitar o uso de matrizes para armazenar meus valores, pois minha consulta será menos legível dessa maneira (eu entendo que isso reduziria o custo dos 23 bytes por sobrecarga de tupla, reduzindo o número de tuplas).
Respostas:
Não necessariamente - ter um índice de btree 'coberto' será o tempo de leitura mais rápido e, se é tudo o que você deseja (por exemplo, se você puder pagar pelo armazenamento extra), é a sua melhor aposta.
Se você não puder pagar a sobrecarga de armazenamento de um índice btree de cobertura, o BRIN é ideal para você, porque você já possui um cluster (isso é crucial para o BRIN ser útil). Os índices BRIN são pequenos , portanto é provável que todas as páginas estejam na memória se você escolher um valor adequado de
pages_per_range
.Nenhuma fórmula mágica, mas comece com
pages_per_range
um pouco menos do que o tamanho médio (em páginas) ocupado peloa
valor médio . Você provavelmente está tentando minimizar: (número de páginas BRIN digitalizadas) + (número de páginas heap digitalizadas) para uma consulta típica. ProcureHeap Blocks: lossy=n
no plano de execuçãopages_per_range=1
e compare com outros valores parapages_per_range
- ou seja, veja quantos blocos de heap desnecessários estão sendo varridos.Vale a pena considerar o GIN, mas provavelmente não o GiST - no entanto, se o agrupamento natural realmente for bom, o BRIN provavelmente será uma aposta melhor.
Aqui está uma amostra de comparação entre os diferentes tipos de índice para dados fictícios, um pouco como o seu:
tabela e índices:
tamanhos de relação:
cobrindo btree:
btree simples:
BRIN pages_per_range = 4:
BRIN pages_per_range = 2:
GIN:
dbfiddle aqui
fonte
Bitmap Index Scan
como o significado 'leia todo o índice brin', mas talvez seja a leitura errada. O OracleCOMPRESS
parece algo que seria útil aqui, pois reduziria o tamanho da árvore B, mas estou preso com a página!Além de btree e brin, que parecem as opções mais sensatas, algumas outras opções exóticas que podem valer a pena investigar - elas podem ser úteis ou não no seu caso:
INCLUDE
índices . Eles estarão - esperançosamente - na próxima versão principal (10) do Postgres, em algum momento por volta de setembro de 2017. Um índice em(a) INCLUDE (b)
tem a mesma estrutura de um índice,(a)
mas inclui nas páginas da folha todos os valores deb
(mas não ordenado). O que significa que você não pode usá-lo, por exemplo, paraSELECT * FROM t WHERE a = 'a' AND b = 2 ;
. O índice pode ser usado, mas enquanto um(a,b)
índice localizar as linhas correspondentes com uma única pesquisa, o índice de inclusão precisará passar pelos valores (possivelmente 100 K, como no seu caso) que correspondema = 'a'
e verificam osb
valores.Por outro lado, o índice é um pouco menos amplo que o
(a,b)
índice e você não precisa do pedidob
para a sua consulta calcularSUM(b)
. Você também pode ter, por exemplo(a) INCLUDE (b,c,d)
que pode ser usado para consultas semelhantes às suas, agregadas nas três colunas.Índices filtrados (parciais) . Uma sugestão que pode parecer um pouco louca * no início:
Um índice para cada
a
valor. No seu caso, cerca de 100K índices. Embora isso pareça muito, considere que cada índice será muito pequeno, tanto em tamanho (número de linhas) quanto em largura (pois armazenará apenasb
valores). Em todos os outros aspectos, ele (os 100K índices juntos) atuará como um índice de árvore b(a,b)
enquanto estiver usando o espaço de um(b)
índice.A desvantagem é que você terá que criar e mantê-los, sempre que um novo valor
a
for adicionado à tabela. Como sua tabela é bastante estável, sem muitas (ou nenhuma) inserções / atualizações, isso não parece ser um problema.Tabelas de resumo. Como a tabela é bastante estável, você sempre pode criar e preencher uma tabela de resumo com os agregados mais comuns necessários (
sum(b), sum(c), sum(d), avg(b), count(distinct b)
etc). Será pequeno (apenas 100 mil linhas) e precisará ser preenchido apenas uma vez e atualizado somente quando as linhas forem inseridas / atualizadas / excluídas na tabela principal.*: ideia copiada desta empresa que executa 10 milhões de índices em seu sistema de produção: The Heap: executando 10 milhões de índices Postgresql em produção (e contando) .
fonte
SUM
como exemplo, mas, na prática, minhas consultas não podem ser pré-computadas (elas são mais parecidasselect ... from t where a = '?' and ??
com as??
que seriam outras condições definidas pelo usuário.??
é;)DO
declaração nesta resposta relacionada .