¿Cómo acelerar seleccionar distinto?


16

Tengo una selección simple distinta en algunos datos de series temporales:

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';

Y lleva 112 segundos. Aquí está el plan de consulta:

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

Mi aplicación tiene que realizar muchas operaciones distintas y cuenta así. ¿Hay alguna forma más rápida de obtener este tipo de datos?

Respuestas:


19

Probablemente no quiera escuchar esto, pero la mejor opción para acelerar SELECT DISTINCTes evitar DISTINCT comenzar. En muchos casos (¡no en todos!) Se puede evitar con un mejor diseño de la base de datos o mejores consultas.

A veces, GROUP BYes más rápido, porque toma una ruta de código diferente.

En su caso particular , no parece que pueda deshacerse de él DISTINCT. Pero podría admitir la consulta con un índice especializado si tiene muchas consultas de ese tipo:

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

Agregar user_idsolo es útil si obtiene escaneos de solo índice de esto. Sigue el enlace para más detalles. Eliminaría el costoso análisis de montón de mapa de bits de su plan de consulta, que consume el 90% del tiempo de consulta.

Su EXPLAINresultado me dice que la consulta tiene que condensar 2.491 usuarios distintos de medio millón de filas coincidentes. Esto no se volverá súper rápido, sin importar lo que haga, pero puede ser sustancialmente más rápido.

Si los intervalos de tiempo en sus consultas son siempre los mismos, un MATERIALIIZED VIEWplegado user_idpor cada vez (project_id, <fixed time intervall>)sería de gran ayuda. Sin embargo, no hay posibilidades con intervalos de tiempo variables. Quizás al menos podría doblar a los usuarios por hora o alguna otra unidad de tiempo mínima, y ​​eso compraría suficiente rendimiento para garantizar la considerable sobrecarga.

Nitpick:
Muy probablemente, los predicados en "time"realidad deberían ser:

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

Aparte:
no utilizar timecomo identificador. Es una palabra reservada en SQL estándar y un tipo básico en Postgres.


He leído un poco sobre escaneos de índice, lo intentaré.
Sam

Desafortunadamente, el intervalo de tiempo no es fijo.
Sam

@Sam: Entonces, ¿cuánto más rápido llegó su consulta de ejemplo con el índice sugerido?
Erwin Brandstetter

3
@edwin: Todavía no he probado la producción. Sin embargo, ejecuté la consulta original en mi local (con los mismos datos) y tardó 3678.780 ms. Luego agregué el índice y lo aceleró hasta 170.156 ms. Plan ahora contiene 'Escaneo de solo índice usando foo en eventos'.
Sam

1
@Sam: ¡Qué bien! Eso es lo que estaba buscando.
Erwin Brandstetter

2

Aquí está mi prueba sobre el caso de Sam y la respuesta 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 dijo: "Probablemente no quiera escuchar esto, pero la mejor opción para acelerar SELECT DISTINCT es evitar DISTINCT para empezar. En muchos casos (¡no en todos!) Puede evitarse con un mejor diseño de la base de datos o mejores consultas ". Creo que tiene razón, debemos evitar el uso de "distinto, agrupar por, ordenar por" (si corresponde).

Conocí una situación como el caso de Sam y creo que Sam puede usar la partición en la tabla de eventos por mes. Reducirá el tamaño de sus datos cuando realice una consulta, pero necesita una función (pl / pgsql) para ejecutar en lugar de la consulta anterior. La función encontrará las particiones apropiadas (dependen de las condiciones) para ejecutar la consulta.


2
> Creo que tiene razón, deberíamos evitar usar "distinto, agrupar por, ordenar por", y también SELECCIONAR, INSERTAR y ACTUALIZAR. Si evitamos estas construcciones, nuestra base de datos será muy rápida.
greatvovan
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.