Fazendo esta pergunta, especificamente para o Postgres, pois possui boa sustentação para índices R-tree / espaciais.
Temos a tabela a seguir com uma estrutura em árvore (modelo Nested Set) de palavras e suas frequências:
lexikon
-------
_id integer PRIMARY KEY
word text
frequency integer
lset integer UNIQUE KEY
rset integer UNIQUE KEY
E a consulta:
SELECT word
FROM lexikon
WHERE lset BETWEEN @Low AND @High
ORDER BY frequency DESC
LIMIT @N
Suponho que um índice de cobertura (lset, frequency, word)
seria útil, mas acho que pode não ter um bom desempenho se houver muitos lset
valores no (@High, @Low)
intervalo.
(frequency DESC)
Às vezes, um índice simples ativado também pode ser suficiente, quando uma pesquisa usando esse índice gera antecipadamente as @N
linhas que correspondem à condição do intervalo.
Mas parece que o desempenho depende muito dos valores dos parâmetros.
Existe uma maneira de fazê-lo funcionar rapidamente, independentemente de o intervalo (@Low, @High)
ser amplo ou estreito e de as palavras de frequência mais altas estarem afortunadamente no intervalo selecionado (estreito)?
Um índice de árvore R / espacial ajudaria?
Adicionando índices, reescrevendo a consulta, redesenhando a tabela, não há limitações.
fonte
lset,rset
eword
.Respostas:
Você pode conseguir um melhor desempenho pesquisando primeiro nas linhas com frequências mais altas. Isso pode ser conseguido 'granulando' as frequências e depois percorrendo-as proceduralmente, por exemplo, da seguinte maneira:
--dados de teste e
lexikon
fictícios:granule
análise (principalmente para informações e ajustes):função para digitalizar altas frequências primeiro:
resultados (os horários provavelmente devem ser tomados com uma pitada de sal, mas cada consulta é executada duas vezes para combater qualquer cache)
primeiro usando a função que escrevemos:
e depois com uma simples verificação de índice:
Dependendo dos dados do mundo real, você provavelmente desejará variar o número de grânulos e a função usada para inserir linhas neles. A distribuição real de frequências é fundamental aqui, assim como os valores esperados para a
limit
cláusula e o tamanho doslset
intervalos procurados.fonte
width_granule=8
entregranulae_start
egranulae_end
do nível anterior?frequency
é gerado: uma grande lacuna entre 1e6 / 2 e 1e6 / 3, quanto maior o número de linhas, maior a diferença. De qualquer forma, obrigado por essa abordagem incrível!Configuração
Estou desenvolvendo a configuração do @ Jack para facilitar o acompanhamento e a comparação das pessoas. Testado com PostgreSQL 9.1.4 .
A partir daqui, tomo uma rota diferente:
Mesa auxiliar
Esta solução não adiciona colunas à tabela original, apenas precisa de uma pequena tabela auxiliar. Coloquei-o no esquema
public
, use qualquer esquema de sua escolha.A tabela fica assim:
Como a coluna
cond
será usada no SQL dinâmico mais adiante, é necessário tornar essa tabela segura . Sempre qualifique a tabela com esquema se você não puder ter certeza de uma corrente apropriadasearch_path
e revogue os privilégios de gravação depublic
(e de qualquer outra função não confiável):A tabela
lex_freq
serve para três propósitos:Índices
Esta
DO
declaração cria todos os índices necessários:Todos esses índices parciais juntos abrangem a tabela uma vez. Eles têm o mesmo tamanho de um índice básico em toda a tabela:
Apenas 21 MB de índices para a tabela de 50 MB até agora.
Eu crio a maioria dos índices parciais
(lset, frequency DESC)
. A segunda coluna ajuda apenas em casos especiais. Porém, como as duas colunas envolvidas são do tipointeger
, devido às especificidades do alinhamento de dados em combinação com MAXALIGN no PostgreSQL, a segunda coluna não aumenta o índice. É uma pequena vitória por quase nenhum custo.Não faz sentido fazer isso para índices parciais que abrangem apenas uma única frequência. Aqueles estão apenas começando
(lset)
. Os índices criados são assim:Função
A função é um pouco semelhante à solução @ Jack:
Principais diferenças:
SQL dinâmico com
RETURN QUERY EXECUTE
.À medida que percorremos as etapas, um plano de consulta diferente pode ser beneficiário. O plano de consulta para SQL estático é gerado uma vez e depois reutilizado - o que pode economizar um pouco de sobrecarga. Mas, neste caso, a consulta é simples e os valores são muito diferentes. SQL dinâmico será uma grande vitória.
Dinâmico
LIMIT
para cada etapa da consulta.Isso ajuda de várias maneiras: Primeiro, as linhas são buscadas apenas conforme necessário. Em combinação com o SQL dinâmico, isso também pode gerar planos de consulta diferentes para começar. Segundo: Não é necessário um adicional
LIMIT
na chamada de função para reduzir o excedente.Referência
Configuração
Escolhi quatro exemplos e fiz três testes diferentes com cada um. Tirei o melhor de cinco para comparar com o cache quente:
A consulta SQL bruta do formulário:
O mesmo depois de criar este índice
Precisa aproximadamente do mesmo espaço que todos os meus índices parciais juntos:
A função
Resultados
1: Tempo de execução total: 315,458 ms
2: Tempo de execução total: 36,458 ms
3: Tempo de execução total: 0,330 ms
1: Tempo de execução total: 294,819 ms
2: Tempo de execução total: 18,915 ms
3: Tempo de execução total: 1,414 ms
1: Tempo de execução total: 426,831 ms
2: Tempo de execução total: 217,874 ms
3: Tempo de execução total: 1,611 ms
1: Tempo de execução total: 2458,205 ms
2: Tempo de execução total: 2458,205 ms - para grandes faixas de lset, a verificação seq é mais rápida que o índice.
3: Tempo de execução total: 0,266 ms
Conclusão
Como esperado, o benefício da função cresce com intervalos maiores
lset
e menoresLIMIT
.Com intervalos muito pequenos de
lset
, a consulta bruta em combinação com o índice é realmente mais rápida . Você deseja testar e talvez ramificar: consulta bruta para pequenos intervalos delset
, ou outra chamada de função. Você pode até incorporar isso na função para um "melhor dos dois mundos" - é o que eu faria.Dependendo da distribuição dos dados e das consultas típicas, mais etapas
lex_freq
podem ajudar no desempenho. Teste para encontrar o ponto ideal. Com as ferramentas apresentadas aqui, deve ser fácil testar.fonte
Não vejo motivo para incluir a coluna da palavra no índice. Então esse índice
fará com que sua consulta seja executada rapidamente.
UPD
Atualmente, não há maneiras de criar um índice de cobertura no PostgreSQL. Houve uma discussão sobre esse recurso na lista de discussão do PostgreSQL http://archives.postgresql.org/pgsql-performance/2012-06/msg00114.php
fonte
Usando um índice GIST
Depende do que você quer dizer quando você jejua: obviamente, você precisa visitar todas as linhas do intervalo porque sua consulta é
ORDER freq DESC
. Apesar disso, o planejador de consultas já cobre isso se eu entender a pergunta,Aqui, criamos uma tabela com 10 mil linhas de
(5::int,random()::double precision)
Nós indexamos,
Nós consultamos,
Nós temos um
Seq Scan on t
. Isso ocorre simplesmente porque nossas estimativas de seletividade permitem que a página conclua que o acesso ao heap é mais rápido do que verificar um índice e verificar novamente. Portanto, tornamos mais interessante inserindo mais 1.000.000 de linhas(42::int,random()::double precision)
que não se encaixam no nosso "intervalo".E então solicitamos,
Você pode ver aqui o preenchimento em 4.6 MS com uma varredura de índice apenas ,
Expandir o intervalo para incluir a tabela inteira, produz outra varredura seq - logicamente, e aumentá-la com mais um bilhão de linhas produziria outra varredura de índice.
Então, em resumo,
fonte