postgresql COUNT (DISTINCT ...) muy lento

166

Tengo una consulta SQL muy simple:

SELECT COUNT(DISTINCT x) FROM table;

Mi mesa tiene aproximadamente 1.5 millones de filas. Esta consulta se ejecuta muy lentamente; toma alrededor de 7.5s, en comparación con

 SELECT COUNT(x) FROM table;

que dura unos 435 ms. ¿Hay alguna forma de cambiar mi consulta para mejorar el rendimiento? He intentado agrupar y hacer un conteo regular, así como poner un índice en x; ambos tienen el mismo tiempo de ejecución de 7.5s.

ferson2020
fuente
No lo creo. Obtener los distintos valores de 1,5 millones de filas será lento.
Ry-
55
Acabo de probarlo en C #, obtener los valores distintos de 1,5 millones de enteros de la memoria lleva más de un segundo en mi computadora. Así que creo que probablemente no tengas suerte.
Ry-
El plan de consulta dependerá en gran medida de la estructura de la tabla (índices) y la configuración de las constantes de ajuste (trabajo) mem, efectivo_caché_size, random_page_cost). Con un ajuste razonable, la consulta podría ejecutarse en menos de un segundo.
wildplasser
¿Podrías ser más específico? ¿Qué índices y constantes de ajuste serían necesarios para obtenerlo en menos de un segundo? Por simplicidad, suponga que esta es una tabla de dos columnas con una clave primaria en la primera columna y, y estoy haciendo esta consulta 'distinta' en una segunda columna x de tipo int, con 1.5 millones de filas.
ferson2020
1
Incluya la definición de la tabla con todos los índices (el \dresultado psqles bueno) y especifique la columna con la que tiene problemas. Sería bueno ver EXPLAIN ANALYZEambas consultas.
vyegorov

Respuestas:

316

Puedes usar esto:

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

Esto es mucho más rápido que:

COUNT(DISTINCT column_name)
Ankur
fuente
38
consultas santas batman! ¡Esto aceleró mi recuento de postgres distintos de 190 a 4.5 whoa!
rogerdpack
20
Encontré este hilo en www.postgresql.org que trata sobre lo mismo: enlace . Una de las respuestas (por Jeff Janes) dice que COUNT (DISTINCT ()) ordena la tabla para hacer su trabajo en lugar de usar hash.
Ankur
55
@Ankur ¿Puedo hacerte una pregunta? Dado que COUNT(DISTINCT())realiza la clasificación, definitivamente será útil tener un índice en column_nameespecial con una cantidad relativamente pequeña de work_mem(donde el hash producirá cantidades relativamente grandes de lotes). Desde entonces, no siempre es malo usar COUNT (DISTINCT () _, ¿no?)
St.Antario
2
@musmahn Count(column)solo cuenta valores no nulos. count(*)cuenta filas. Entonces, el primero / más largo, también contará la fila nula (una vez). Cambie a count(column_name)para que se comporten igual.
GolezTrol
1
@ankur esto no fue muy útil para mí ... no obtuve ninguna mejora notable.
Shiwangini
11
-- 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 para el CTE probablemente también podría ser producido por otros métodos (funciones de ventana)

wildplasser
fuente
2
¿Has considerado el efecto del almacenamiento en caché? Si se realizan tres "explicaciones, análisis" posteriormente, la primera puede ser una recuperación lenta desde el disco, mientras que la segunda puede ser una recuperación rápida de la memoria.
tobixen
De hecho: efectividad_cache_size es la primera configuración para ajustar. El mío es de 2 GB, IIRC.
wildplasser
Establecí mi efectividad_cache_size a 2GB, sin cambios en el rendimiento. ¿Alguna otra configuración que sugiera ajustar? Si es así, ¿a qué?
ferson2020
1) ¿Cómo lo configuraste? (¿LO HUPAS?) 2) ¿De verdad tienes tanta memoria disponible? 3) muéstranos tu plan. 4) tal vez mi máquina sea más rápida o la suya tenga más carga simultánea con la que lidiar. @ ferson2020: Ok
wildplasser
Lo configuré con la declaración: SET efectivo_cache_size = '2GB'; Tengo tanta memoria disponible. Intenté incluir mi plan de consulta, pero no cabe en el cuadro de comentarios.
ferson2020
2

Si tu count(distinct(x))es significativamente más lento que count(x)entonces, puedes acelerar esta consulta manteniendo los conteos de valores x en una tabla diferente, por ejemplo table_name_x_counts (x integer not null, x_count int not null), usando disparadores. Pero su rendimiento de escritura se verá afectado y si actualiza varios xvalores en una sola transacción, deberá hacer esto en un orden explícito para evitar un posible punto muerto.

Tometzky
fuente
0

También estaba buscando la misma respuesta, porque en algún momento necesitaba total_count con valores distintos junto con limit / offset .

Porque es un poco difícil de hacer: para obtener el recuento total con valores distintos junto con límite / desplazamiento. Por lo general, es difícil obtener el recuento total con límite / compensación. Finalmente conseguí la forma de hacerlo:

SELECT DISTINCT COUNT(*) OVER() as total_count, * FROM table_name limit 2 offset 0;

El rendimiento de las consultas también es alto.

Rana Pratap Singh
fuente