Exploración secuencial de PostgreSQL en lugar de exploración de índice ¿Por qué?

11

Hola a todos Tengo un problema con mi consulta de base de datos PostgreSQL y me pregunto si alguien puede ayudar. En algunos escenarios, mi consulta parece ignorar el índice que he creado que se utiliza para unir las dos tablas datay data_area. Cuando esto sucede, utiliza una exploración secuencial y da como resultado una consulta mucho más lenta.

Escaneo secuencial (~ 5 minutos)

Unique  (cost=15368261.82..15369053.96 rows=200 width=1942) (actual time=301266.832..301346.936 rows=153812 loops=1)
   CTE data
     ->  Bitmap Heap Scan on data  (cost=6086.77..610089.54 rows=321976 width=297) (actual time=26.286..197.625 rows=335130 loops=1)
           Recheck Cond: (datasetid = 1)
           Filter: ((readingdatetime >= '1920-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2013-03-11 00:00:00'::timestamp without time zone) AND (depth >= 0::double precision) AND (depth <= 99999::double precision))
           ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..6006.27 rows=324789 width=0) (actual time=25.462..25.462 rows=335130 loops=1)
                 Index Cond: (datasetid = 1)
   ->  Sort  (cost=15368261.82..15368657.89 rows=158427 width=1942) (actual time=301266.829..301287.110 rows=155194 loops=1)
         Sort Key: data.id
         Sort Method: quicksort  Memory: 81999kB
         ->  Hash Left Join  (cost=15174943.29..15354578.91 rows=158427 width=1942) (actual time=300068.588..301052.832 rows=155194 loops=1)
               Hash Cond: (data_area.area_id = area.id)
               ->  Hash Join  (cost=15174792.93..15351854.12 rows=158427 width=684) (actual time=300066.288..300971.644 rows=155194 loops=1)
                     Hash Cond: (data.id = data_area.data_id)
                     ->  CTE Scan on data  (cost=0.00..6439.52 rows=321976 width=676) (actual time=26.290..313.842 rows=335130 loops=1)
                     ->  Hash  (cost=14857017.62..14857017.62 rows=25422025 width=8) (actual time=300028.260..300028.260 rows=26709939 loops=1)
                           Buckets: 4194304  Batches: 1  Memory Usage: 1043357kB
                           ->  Seq Scan on data_area  (cost=0.00..14857017.62 rows=25422025 width=8) (actual time=182921.056..291687.996 rows=26709939 loops=1)
                                 Filter: (area_id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
               ->  Hash  (cost=108.49..108.49 rows=3349 width=1258) (actual time=2.256..2.256 rows=3349 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 584kB
                     ->  Seq Scan on area  (cost=0.00..108.49 rows=3349 width=1258) (actual time=0.007..0.666 rows=3349 loops=1)
 Total runtime: 301493.379 ms

Escaneo de índice (~ 3 segundos) ( en explicar.depesz.com )

Unique  (cost=17352256.47..17353067.50 rows=200 width=1942) (actual time=3603.303..3681.619 rows=153812 loops=1)
   CTE data
     ->  Bitmap Heap Scan on data  (cost=6284.60..619979.56 rows=332340 width=297) (actual time=26.201..262.314 rows=335130 loops=1)
           Recheck Cond: (datasetid = 1)
           Filter: ((readingdatetime >= '1920-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2013-03-11 00:00:00'::timestamp without time zone) AND (depth >= 0::double precision) AND (depth <= 99999::double precision))
           ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..6201.51 rows=335354 width=0) (actual time=25.381..25.381 rows=335130 loops=1)
                 Index Cond: (datasetid = 1)
   ->  Sort  (cost=17352256.47..17352661.98 rows=162206 width=1942) (actual time=3603.302..3623.113 rows=155194 loops=1)
         Sort Key: data.id
         Sort Method: quicksort  Memory: 81999kB
         ->  Hash Left Join  (cost=1296.08..17338219.59 rows=162206 width=1942) (actual time=29.980..3375.921 rows=155194 loops=1)
               Hash Cond: (data_area.area_id = area.id)
               ->  Nested Loop  (cost=0.00..17334287.66 rows=162206 width=684) (actual time=26.903..3268.674 rows=155194 loops=1)
                     ->  CTE Scan on data  (cost=0.00..6646.80 rows=332340 width=676) (actual time=26.205..421.858 rows=335130 loops=1)
                     ->  Index Scan using data_area_pkey on data_area  (cost=0.00..52.13 rows=1 width=8) (actual time=0.006..0.008 rows=0 loops=335130)
                           Index Cond: (data_id = data.id)
                           Filter: (area_id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
               ->  Hash  (cost=1254.22..1254.22 rows=3349 width=1258) (actual time=3.057..3.057 rows=3349 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 584kB
                     ->  Index Scan using area_primary_key on area  (cost=0.00..1254.22 rows=3349 width=1258) (actual time=0.012..1.429 rows=3349 loops=1)
 Total runtime: 3706.630 ms

Estructura de la mesa

Esta es la estructura de la data_areatabla para la tabla. Puedo proporcionar las otras tablas si es necesario.

CREATE TABLE data_area
(
  data_id integer NOT NULL,
  area_id integer NOT NULL,
  CONSTRAINT data_area_pkey PRIMARY KEY (data_id , area_id ),
  CONSTRAINT data_area_area_id_fk FOREIGN KEY (area_id)
      REFERENCES area (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT data_area_data_id_fk FOREIGN KEY (data_id)
      REFERENCES data (id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
);

CONSULTA

WITH data AS (
    SELECT * 
    FROM data 
    WHERE 
        datasetid IN (1) 
        AND (readingdatetime BETWEEN '1920-01-01' AND '2013-03-11') 
        AND depth BETWEEN 0 AND 99999
)
SELECT * 
FROM ( 
    SELECT DISTINCT ON (data.id) data.id, * 
    FROM 
        data, 
        data_area 
        LEFT JOIN area ON area_id = area.id 
    WHERE 
        data_id = data.id 
        AND area_id IN (28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 
) as s;

Devuelve 153812filas. Hizo set enable_seqscan= false;para deshabilitar la exploración secuencial y obtener el resultado del índice.

Intenté hacer un ANALYSEen la base de datos y aumentar las estadísticas recopiladas en las columnas utilizadas en la consulta, pero nada parece ayudar.

¿Podría alguien difundir y aclarar esto o sugerir algo más que debería intentar?

Mark Davidson
fuente
Sería de gran ayuda que me si se incluye las consultas que generan cada uno de los planes de ejecución.
Mike Sherrill 'Cat Recall'
¿Una diferencia de 2 órdenes de magnitud en el número estimado de filas y el número real de filas? ¿Estoy leyendo eso bien?
Mike Sherrill 'Cat Recall'
@Catcall Han agregado la consulta (algo fundamental para poder resolver lo que está sucediendo). Cuando se refiere a las filas estimadas, ¿es que el 200 y luego en realidad devuelve 153812?
Mark Davidson
2
Sí, 200 vs 150k parece extraño de un vistazo. ¿Hay alguna razón convincente para mezclar una combinación izquierda con un producto cartesiano ( FROM data, data_area)? A primera vista, usar DISTINCT ON sin una cláusula ORDER BY parece ser una mala idea.
Mike Sherrill 'Cat Recall'
explica.depesz.com/s/Uzin puede ser informativo.
Craig Ringer

Respuestas:

7

Observe esta línea:

->  Index Scan using data_area_pkey on data_area  (cost=0.00..52.13 rows=1 width=8) 
    (actual time=0.006..0.008 rows=0 loops=335130)

Si calcula el costo total, considerando los bucles, lo es 52.3 * 335130 = 17527299. Esto es mayor que 14857017.62 para la seq_scanalternativa. Es por eso que no usa el índice.

Por lo tanto, el optimizador está sobreestimando el costo de la exploración del índice. Supongo que sus datos se ordenan en el índice (ya sea debido a un índice agrupado o a cómo se cargó) y / o tiene mucha memoria caché y / o un buen disco rápido. Por lo tanto, hay pocas E / S aleatorias.

También debe comprobar el correlationen pg_stats, que es utilizado por el optimizador para evaluar la agrupación cuando se calcula el costo del índice, y, finalmente, tratar de cambiar random_page_costy cpu_index_tuple_cost, para que coincida con su sistema.

jop
fuente
A menos que me falta algo, creo @jop significaba 52.13, no 52.3, lo que resultaría en 17470326.9 (aún más grande que el seq_scan)
BotNet
2

Su CTE en realidad no hace nada más que 'externalizar' algunas WHEREcondiciones, la mayoría de ellas con un aspecto equivalente WHERE TRUE. Dado que los CTE generalmente están detrás de una valla de optimización (lo que significa que está optimizado por sí solo), pueden ayudar mucho con ciertas consultas. En este caso, sin embargo, esperaría exactamente el efecto contrario.

Lo que intentaría es reescribir la consulta para que sea lo más simple posible:

SELECT d.id, * 
FROM 
    data d 
    JOIN data_area da ON da.data_id = d.id
    LEFT JOIN area a ON da.area_id = a.id 
WHERE 
    d.datasetid IN (1) 
    AND da.area_id IN (28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 
    AND (readingdatetime BETWEEN '1920-01-01' AND '2013-03-11') -- this and the next condition don't do anything, I think
    AND depth BETWEEN 0 AND 99999
;

y luego verifique si el índice se usa o no. Todavía es muy posible que no necesite todas las columnas de salida (al menos las dos columnas de la tabla de unión son superfluas).

Informe y díganos qué versión de PostgreSQL usa.

dezso
fuente
Gracias por su sugerencia, mis disculpas por mi respuesta tardía a su publicación, he estado trabajando en otros proyectos. Su sugerencia realmente significa que la consulta ahora parece usar de manera confiable el índice para todas las consultas, pero todavía no obtengo el rendimiento que esperaría con ella. He realizado un análisis en una consulta que tiene muchos más datos. Explica.depesz.com/s/1yu toma como 4 minutos, con el 95% del tiempo dedicado al análisis INDEX.
Mark Davidson
Olvidé mencionar que estoy usando la versión 9.1.4
Mark Davidson
Básicamente, el escaneo del índice es bastante rápido, el problema es que se repite unos pocos millones de veces. ¿Qué obtienes si SET enable_nestloop=offantes de ejecutar la consulta?
dezso
-1

Para los seguidores, tuve un problema similar que era como

select * from table where bigint_column between x and y and mod(bigint_column, 10000) == z

El problema era que mi bigint_column "entre x e y" tenía un índice, pero mi consulta era básicamente "todas las filas" en esa tabla, por lo que no estaba usando el índice [ya que tenía que escanear toda la tabla de todos modos] pero estaba haciendo una exploración secuencial seq_scan. Una solución para mí fue crear un nuevo índice para el lado "mod" de la ecuación, de modo que pudiera usarlo en una expresión .

rogerdpack
fuente
los
votantes negativos