2012-06-28 22 views
99

Tengo una muy simple consulta SQL:RECUENTO PostgreSQL (DISTINCT ...) muy lento

SELECT COUNT(DISTINCT x) FROM table; 

Mi tabla tiene alrededor de 1,5 millones de filas. Esta consulta se ejecuta bastante lentamente; se tarda alrededor de 7.5s, en comparación con

SELECT COUNT(x) FROM table; 

que toma alrededor de 435ms. ¿Hay alguna manera de cambiar mi consulta para mejorar el rendimiento? Intenté agrupar y hacer un conteo regular, así como también poner un índice en x; ambos tienen el mismo tiempo de ejecución de 7.5s.

+0

Yo no lo creo. Obtener los valores distintivos de 1,5 millones de filas va a ser lento. – Ryan

+4

Acabo de probarlo en C#, obteniendo los distintos valores de 1.5 millones * enteros de la memoria * toma más de un segundo en mi computadora. Entonces creo que probablemente no tengas suerte. – Ryan

+0

El plan de consulta dependerá en gran medida de la estructura de la tabla (índices) y la configuración de las constantes de sintonía (trabajo) mem, effective_cache_size, random_page_cost). Con un ajuste razonable, la consulta podría ejecutarse en menos de un segundo. – wildplasser

Respuesta

8
-- My default settings (this is basically a single-session machine, so work_mem is pretty high) 
SET effective_cache_size='2048MB'; 
SET work_mem='16MB'; 

\echo original 
EXPLAIN ANALYZE 
SELECT 
     COUNT (distinct val) as aantal 
FROM one 
     ; 

\echo group by+count(*) 
EXPLAIN ANALYZE 
SELECT 
     distinct val 
     -- , COUNT(*) 
FROM one 
GROUP BY val; 

\echo with CTE 
EXPLAIN ANALYZE 
WITH agg AS (
    SELECT distinct val 
    FROM one 
    GROUP BY val 
    ) 
SELECT COUNT (*) as aantal 
FROM agg 
     ; 

Resultados:

original              QUERY PLAN              
---------------------------------------------------------------------------------------------------------------------- 
Aggregate (cost=36448.06..36448.07 rows=1 width=4) (actual time=1766.472..1766.472 rows=1 loops=1) 
    -> Seq Scan on one (cost=0.00..32698.45 rows=1499845 width=4) (actual time=31.371..185.914 rows=1499845 loops=1) 
Total runtime: 1766.642 ms 
(3 rows) 

group by+count(*) 
                 QUERY PLAN               
---------------------------------------------------------------------------------------------------------------------------- 
HashAggregate (cost=36464.31..36477.31 rows=1300 width=4) (actual time=412.470..412.598 rows=1300 loops=1) 
    -> HashAggregate (cost=36448.06..36461.06 rows=1300 width=4) (actual time=412.066..412.203 rows=1300 loops=1) 
     -> Seq Scan on one (cost=0.00..32698.45 rows=1499845 width=4) (actual time=26.134..166.846 rows=1499845 loops=1) 
Total runtime: 412.686 ms 
(4 rows) 

with CTE 
                  QUERY PLAN                
------------------------------------------------------------------------------------------------------------------------------------ 
Aggregate (cost=36506.56..36506.57 rows=1 width=0) (actual time=408.239..408.239 rows=1 loops=1) 
    CTE agg 
    -> HashAggregate (cost=36464.31..36477.31 rows=1300 width=4) (actual time=407.704..407.847 rows=1300 loops=1) 
      -> HashAggregate (cost=36448.06..36461.06 rows=1300 width=4) (actual time=407.320..407.467 rows=1300 loops=1) 
       -> Seq Scan on one (cost=0.00..32698.45 rows=1499845 width=4) (actual time=24.321..165.256 rows=1499845 loops=1) 
     -> CTE Scan on agg (cost=0.00..26.00 rows=1300 width=0) (actual time=407.707..408.154 rows=1300 loops=1) 
    Total runtime: 408.300 ms 
    (7 rows) 

El mismo plan que el CTE probablemente podría también ser producida por otros métodos (funciones de ventanas)

+2

¿Ha considerado el efecto del almacenamiento en caché? Si se realizan tres "explicaciones analizadas" posteriormente, el primero puede ser lento para obtener cosas del disco, mientras que las dos últimas pueden ser extraídas rápidamente de la memoria. – tobixen

+0

De hecho: effective_cache_size es la primera configuración para ajustar. El mío es 2GB, IIRC. – wildplasser

+0

Configuré effective_cache_size en 2 GB, sin cambios en el rendimiento. ¿Alguna otra configuración que sugieras modificar? Si es así, ¿a qué? – ferson2020

1

Si su count(distinct(x)) es significativamente más lento que count(x) entonces se puede agilice esta consulta manteniendo conteos de valores x en tablas diferentes, por ejemplo table_name_x_counts (x integer not null, x_count int not null), utilizando desencadenantes. Pero su rendimiento de escritura se verá afectado y si actualiza múltiples valores de x en una única transacción, deberá hacerlo en un orden explícito para evitar un posible punto muerto.

201

Puede utilizar este:

SELECT COUNT(*) FROM (SELECT DISTINCT column_name FROM table_name) AS temp; 

Esto es mucho más rápido que:

COUNT(DISTINCT column_name) 
+23

preguntas sagradas batman! Esto aceleró mi recuento de postgres distinto de 190 a 4.5 whoa! – rogerdpack

+11

Me encantaría una explicación de por qué esto funciona. ¡Un gran consejo! – DavidMann10k

+11

Encontré este hilo en [www.postgresql.org] (http://www.postgresql.org) que discute lo mismo: [link] (http://www.postgresql.org/message-id/CAONnt+ [email protected]). Una de las respuestas (por Jeff Janes) dice que COUNT (DISTINCT()) ordena la tabla para hacer su trabajo en lugar de usar hash. – Ankur