Tengo una tabla que contiene aproximadamente 55 millones de puntos de datos (el punto es una geometría con SRID 4326) y para mi consulta necesito unir esto a una tabla de área (actualmente tiene ~ 1800 áreas) que contiene una variedad de diferentes que van desde polígonos grandes ( 2000 km cuadrados) a bastante pequeño (pequeño siendo unos 100 km cuadrados).
La consulta inicial seleccionada por el usuario reduce los 55 millones de puntos iniciales a alrededor de ~ 300,000 puntos dependiendo del rango de fechas, etc. que seleccionen. Luego, la unión se realiza y depende del conjunto de áreas que hayan seleccionado para usar una vez que se complete la consulta, esto normalmente lo reduce a ~ 150,000.
El problema que tengo es que algunas veces la consulta simplemente se detiene y, en lugar de tomar los ~ 25 segundos esperados, puede tomar hasta ~ 18 minutos. En este punto, generalmente tiene que hacer un ANÁLISIS DE VACÍO y luego ejecutar algunas consultas antes de que comience a comportarse nuevamente. No se han agregado, actualizado o eliminado datos de las tablas de datos o áreas en este momento.
He jugado con todo lo que puedo pensar y esto todavía parece seguir sucediendo sin constancia. Tanto la columna data.point como la columna area.polygon tienen ÍNDICES GIST en ellas.
Descubrí que eliminar el ÍNDICE de la columna data.point parecía hacer las cosas un poco más estables, pero normalmente es más lento ~ 35 segundos normalmente. Sin embargo, eliminar un ÍNDICE parece ser una muy mala elección, ya que ¿no debería ayudar a no obstaculizar?
Estoy usando PostgreSQL 9.1.4 con PostGIS 1.5
Aquí está la consulta que estoy ejecutando
select * FROM data, area WHERE st_intersects (data.point, area.polygon) AND
(readingdatetime BETWEEN '1948-01-01' AND '2012-11-19') AND datasetid IN(3) AND
"polysetID" = 1 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)
EXPLIQUE
Nested Loop (cost=312.28..336.59 rows=5 width=2246) (actual time=1445.973..11557.824 rows=12723 loops=1)
Join Filter: _st_intersects(data.point, area.polygon)
-> Index Scan using "area_polysetID_index" on area (cost=0.00..20.04 rows=1 width=1949) (actual time=0.017..0.229 rows=35 loops=1)
Index Cond: ("polysetID" = 1)
Filter: (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[]))
-> Bitmap Heap Scan on data (cost=312.28..316.29 rows=1 width=297) (actual time=328.771..329.136 rows=641 loops=35)
Recheck Cond: ((point && area.polygon) AND (datasetid = 3))"
Filter: ((readingdatetime >= '1948-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2012-11-19 00:00:00'::timestamp without time zone))
-> BitmapAnd (cost=312.28..312.28 rows=1 width=0) (actual time=328.472..328.472 rows=0 loops=35)
-> Bitmap Index Scan on data_point_index (cost=0.00..24.47 rows=276 width=0) (actual time=307.115..307.115 rows=1365770 loops=35)
Index Cond: (point && area.polygon)
-> Bitmap Index Scan on data_datasetid_index (cost=0.00..284.37 rows=12856 width=0) (actual time=1.522..1.522 rows=19486 loops=35)
Index Cond: (datasetid = 3)
Total runtime: 11560.879 ms
Mis tablas de creación
CREATE TABLE data
(
id bigserial NOT NULL,
datasetid integer NOT NULL,
readingdatetime timestamp without time zone NOT NULL,
value double precision NOT NULL,
description character varying(255),
point geometry,
CONSTRAINT "DATAPRIMARYKEY" PRIMARY KEY (id ),
CONSTRAINT enforce_dims_point CHECK (st_ndims(point) = 2),
CONSTRAINT enforce_geotype_point CHECK (geometrytype(point) = 'POINT'::text OR point IS NULL),
CONSTRAINT enforce_srid_point CHECK (st_srid(point) = 4326)
);
CREATE INDEX data_datasetid_index ON data USING btree (datasetid);
ALTER TABLE data CLUSTER ON data_datasetid_index;
CREATE INDEX "data_datasetid_readingDatetime_index" ON data USING btree (datasetid , readingdatetime );
CREATE INDEX data_point_index ON data USING gist (point);
CREATE INDEX "data_readingDatetime_index" ON data USING btree (readingdatetime );
CREATE TABLE area
(
id serial NOT NULL,
polygon geometry,
"polysetID" integer NOT NULL,
CONSTRAINT area_primary_key PRIMARY KEY (id )
)
CREATE INDEX area_polygon_index ON area USING gist (polygon);
CREATE INDEX "area_polysetID_index" ON area USING btree ("polysetID");
ALTER TABLE area CLUSTER ON "area_polysetID_index";
Espero que todo tenga sentido si necesita saber algo más, por favor pregunte.
Un breve resumen es realmente que los ÍNDICES parecen funcionar algunas veces pero no otras.
¿Alguien podría sugerir algo que yo pueda tratar de averiguar qué está pasando?
Gracias por adelantado.
EDITAR:
Otro ejemplo
select * FROM data, area WHERE st_intersects ( data.point, area.polygon) AND
(readingdatetime BETWEEN '2009-01-01' AND '2012-01-19') AND datasetid IN(1,3) AND
"polysetID" = 1 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)
Ejecutar en copia de tabla con índice de puntos
Nested Loop (cost=0.00..1153.60 rows=35 width=2246) (actual time=86835.883..803363.979 rows=767 loops=1)
Join Filter: _st_intersects(data.point, area.polygon)
-> Index Scan using "area_polysetID_index" on area (cost=0.00..20.04 rows=1 width=1949) (actual time=0.021..16.287 rows=35 loops=1)
Index Cond: ("polysetID" = 1)
Filter: (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[]))
-> Index Scan using data_point_index on data (cost=0.00..1133.30 rows=1 width=297) (actual time=17202.126..22952.706 rows=33 loops=35)
Index Cond: (point && area.polygon)
Filter: ((readingdatetime >= '2009-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2012-01-19 00:00:00'::timestamp without time zone) AND (datasetid = ANY ('{1,3}'::integer[])))
Total runtime: 803364.120 ms
Ejecutar en copia de tabla sin índice de puntos
Nested Loop (cost=2576.91..284972.54 rows=34 width=2246) (actual time=181.478..235.608 rows=767 loops=1)
Join Filter: ((data_new2.point && area.polygon) AND _st_intersects(data_new2.point, area.polygon))
-> Index Scan using "area_polysetID_index" on area (cost=0.00..20.04 rows=1 width=1949) (actual time=0.149..0.196 rows=35 loops=1)
Index Cond: ("polysetID" = 1)
Filter: (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[]))
-> Bitmap Heap Scan on data_new2 (cost=2576.91..261072.36 rows=90972 width=297) (actual time=4.808..5.599 rows=2247 loops=35)
Recheck Cond: ((datasetid = ANY ('{1,3}'::integer[])) AND (readingdatetime >= '2009-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2012-01-19 00:00:00'::timestamp without time zone))
-> Bitmap Index Scan on "data_new2_datasetid_readingDatetime_index" (cost=0.00..2554.16 rows=90972 width=0) (actual time=4.605..4.605 rows=2247 loops=35)
Index Cond: ((datasetid = ANY ('{1,3}'::integer[])) AND (readingdatetime >= '2009-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2012-01-19 00:00:00'::timestamp without time zone))
Total runtime: 235.723 ms
Como puede ver, la consulta es significativamente más lenta cuando se utiliza el índice de puntos.
EDITAR 2 (Consulta sugerida por Pauls):
WITH polys AS (
SELECT * FROM area
WHERE "polysetID" = 1 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)
)
SELECT *
FROM polys JOIN data ON ST_Intersects(data.point, polys.polygon)
WHERE datasetid IN(1,3)
AND (readingdatetime BETWEEN '2009-01-01' AND '2012-01-19');
EXPLIQUE
Nested Loop (cost=20.04..1155.43 rows=1 width=899) (actual time=16691.374..279065.402 rows=767 loops=1)
Join Filter: _st_intersects(data.point, polys.polygon)
CTE polys
-> Index Scan using "area_polysetID_index" on area (cost=0.00..20.04 rows=1 width=1949) (actual time=0.016..0.182 rows=35 loops=1)
Index Cond: ("polysetID" = 1)
Filter: (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[]))
-> CTE Scan on polys (cost=0.00..0.02 rows=1 width=602) (actual time=0.020..0.358 rows=35 loops=1)
-> Index Scan using data_point_index on data (cost=0.00..1135.11 rows=1 width=297) (actual time=6369.327..7973.201 rows=33 loops=35)
Index Cond: (point && polys.polygon)
Filter: ((datasetid = ANY ('{1,3}'::integer[])) AND (readingdatetime >= '2009-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2012-01-19 00:00:00'::timestamp without time zone))
Total runtime: 279065.540 ms
fuente
Respuestas:
Forzar eficazmente al planificador a hacer lo que desea podría ayudar. En este caso, subconfigurando la tabla de polígonos antes de ejecutar la unión espacial con la tabla de puntos. Es posible que pueda burlar al planificador con la sintaxis "WITH":
El problema al intentar jugar estos juegos es que está codificando en su declaración la suposición "mi lista de polígonos siempre será más selectiva que mis otras porciones de consulta". Lo que podría no ser cierto para todas las parametrizaciones de su consulta, o para todas las aplicaciones de una consulta en particular en un conjunto de datos distribuido heterogéneamente.
Pero podría funcionar.
ACTUALIZACIÓN : Esto va aún más lejos en el camino peligroso de suponer que conoce la selectividad de sus cláusulas de antemano, esta vez también tomamos la selección de atributos en la tabla de puntos y la hacemos por separado antes de la unión espacial:
fuente
Al observar la explicación para Ejecutar en copia de tabla con índice de puntos en el primer EDITAR, parece que falta este índice en la tabla sin índice de puntos:
¿Puedes confirmar que el índice está ahí?
- EDITAR -
Después de estudiar un poco más su pregunta (no es fácil, por cierto) tengo las siguientes sugerencias para hacer.
agrupe la tabla contra el índice "data_readingDatetime_index". La agrupación es más efectiva con consultas basadas en rango. Parece que no consulta datasetid con condiciones basadas en rango
Realice la agrupación real. El comando del elemento anterior no agrupa su tabla, simplemente expresa su deseo de que si la tabla se agrupara, quisiera que se agrupara en ese índice. Agrúpelo con:
analice la tabla después de agruparla para que las estadísticas (utilizadas por el planificador para decidir qué estrategia elegir) tengan en cuenta el nuevo diseño en el disco:
ahora, dado que los datos se organizan en función de la fecha de lectura, el planificador favorecerá una estrategia en la que se use el índice data_readingDatetime_index y, siempre que se use, el plan de explicación parece ser el más rápido, entonces quizás el rendimiento mejorará y fluctuará menos
Como dije en el comentario a la respuesta anterior de Paul, no piense que el planificador no cambiará la estrategia dependiendo de los filtros (incluso si los filtros son siempre los mismos y solo cambian sus valores).
Hay un ejemplo en el libro altamente recomendado PostregSQL 9.0 High Performance donde cambiar una condición de select ... de la tabla t donde v <5 a v <6 cambió el plan de exploración de índice a exploración de tabla completa.
fuente