Como acelerar selecionar distintos?

16

Eu tenho uma seleção simples e distinta em alguns dados de séries temporais:

SELECT DISTINCT user_id
FROM events
WHERE project_id = 6
AND time > '2015-01-11 8:00:00'
AND time < '2015-02-10 8:00:00';

E leva 112 segundos. Aqui está o plano de consulta:

http://explain.depesz.com/s/NTyA

Meu aplicativo precisa executar muitas operações distintas e conta como este. Existe uma maneira mais rápida de obter esse tipo de dados?

Sam
fonte

Respostas:

19

Você provavelmente não quer ouvir isso, mas a melhor opção para acelerar SELECT DISTINCT é evitar DISTINCT começar. Em muitos casos (nem todos!), Isso pode ser evitado com um melhor design do banco de dados ou com melhores consultas.

Às vezes, GROUP BYé mais rápido, porque leva um caminho de código diferente.

No seu caso particular , não parece que você possa se livrar deDISTINCT . Mas você pode dar suporte à consulta com um índice especializado se tiver muitas consultas desse tipo:

CREATE INDEX foo ON events (project_id, "time", user_id);

A adição user_idé útil apenas se você conseguir verificações somente de índice . Siga o link para detalhes. Removeria o caro verificação de heap de bitmap do seu plano de consulta, que consome 90% do tempo da consulta.

Sua EXPLAINsaída me diz que a consulta precisa condensar 2.491 usuários distintos em meio milhão de linhas correspondentes. Isso não se tornará super-rápido, não importa o que você faça, mas pode ser substancialmente mais rápido.

Se os intervalos de tempo em suas consultas forem sempre os mesmos, uma MATERIALIIZED VIEWdobra user_idpor(project_id, <fixed time intervall>) percorrerá um longo caminho. Não há nenhuma chance com intervalos de tempo variados. Talvez você possa pelo menos dobrar os usuários por hora ou alguma outra unidade de tempo mínima, e isso compraria desempenho suficiente para garantir a sobrecarga considerável.

Nitpick:
Muito provavelmente, os predicados "time"devem realmente ser:

AND "time" >= '2015-01-11 8:00:00'
AND "time" <  '2015-02-10 8:00:00';

Lado:
Não use timecomo identificador. É uma palavra reservada no SQL padrão e um tipo básico no Postgres.

Erwin Brandstetter
fonte
Eu li um pouco sobre varreduras de índice, vou tentar.
Sam
Infelizmente, o intervalo de tempo não é fixo.
Sam
@ Sam: Então, quanto mais rápido sua consulta de exemplo ficou com o índice sugerido?
Erwin Brandstetter
3
@edwin: Ainda não experimentou a produção. No entanto, executei a consulta original no meu local (com os mesmos dados) e demorou 3678.780 ms. Então eu adicionei o índice e ele acelerou até 170.156 ms. O plano agora contém 'Verificação apenas do índice usando foo em eventos'.
Sam
1
@ Sam: Legal! Era para isso que eu estava buscando.
Erwin Brandstetter
2

Aqui está o meu teste no caso de Sam e a resposta de Erwin

drop table t1
create table t1 (id int, user_id int, project_id int, date_time timestamp without time zone) ;

insert into t1 -- 10 million row - size="498 MB"
select row_number() over(), round(row_number() over()/1000), round(row_number() over()/100000) , date
from generate_series('2015-01-01'::date, '2016-12-01'::date,'6 seconds'::interval
) date 
limit 10000000

-- before indexing - 10000000 row - output=100 row - time=2900ms
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 8:00:00'
AND date_time < '2016-12-01 8:00:00' ;

CREATE INDEX foo ON t1 (project_id, date_time, user_id); -- time process=51.2 secs -- size="387 MB"         

-- after indexing - 10000000 row - output=100 row - time= 75ms (reduce ~ 38 times)
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 00:00:00'
AND date_time < '2016-12-01 00:00:00' ;

Erwin disse: "Você provavelmente não quer ouvir isso, mas a melhor opção para acelerar o SELECT DISTINCT é evitar o DISTINCT para começar. Em muitos casos (nem todos!), Isso pode ser evitado com um melhor design do banco de dados ou com melhores consultas. " Acho que ele está certo, devemos evitar usar "distinto, agrupar por, ordenar por" (se houver).

Eu conheci uma situação como o caso de Sam e acho que Sam pode usar partição na tabela de eventos por mês. Reduzirá o tamanho dos dados quando você consultar, mas você precisa de uma função (pl / pgsql) para executar em vez da consulta acima. A função encontrará partições apropriadas (depende das condições) para executar a consulta.

Luan Huynh
fonte
2
> Acho que ele está certo, devemos evitar usar "distinto, agrupar por, ordenar por" - e também SELECT, INSERT e UPDATE. Se evitarmos essas construções, nosso banco de dados será muito rápido!
25818 greatvovan