Eu tenho uma tabela que contém aproximadamente 55 milhões de pontos de dados (point é uma geometria com SRID 4326) e, para minha consulta, preciso associá-la a uma tabela de área (atualmente tem ~ 1800 áreas) que contém uma variedade de diferentes que variam de polígonos grandes ( 2000 km quadrados) a relativamente pequeno (pequeno sendo cerca de 100 km quadrados).
A consulta inicial selecionada pelo usuário reduz os 55 milhões de pontos iniciais para cerca de ~ 300.000 pontos, dependendo do período, etc. Em seguida, a junção é feita e depende da área que eles selecionaram para usar quando a consulta for concluída, normalmente a reduz para ~ 150.000.
O problema que estou tendo é que, algumas vezes, a consulta é interrompida e, em vez de levar os ~ 25 segundos esperados, pode levar até ~ 18 minutos. Neste ponto, é necessário fazer uma ANÁLISE DE VÁCUO e executar algumas consultas antes que ele comece a se comportar novamente. Nenhum dado foi adicionado, atualizado ou removido das tabelas de dados ou áreas neste momento.
Eu brinquei com tudo o que consigo pensar e isso ainda parece continuar acontecendo sem constância também. Tanto a coluna data.point como a coluna area.polygon possuem GIST INDEXES nelas.
Descobri que remover o INDEX da coluna data.point parecia tornar as coisas um pouco mais estáveis, mas fica mais lento ~ 35 segundos normalmente. No entanto, remover um INDEX parece ser uma péssima escolha, pois não deve estar ajudando a não dificultar?
Estou usando o PostgreSQL 9.1.4 com o PostGIS 1.5
Aqui está a consulta que estou executando
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)
EXPLICAR
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
Minhas tabelas de criação
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 tudo faça algum sentido, se precisar saber mais alguma coisa, por favor, pergunte.
Um breve resumo é realmente que os ÍNDICES parecem funcionar algumas vezes, mas não outras.
Alguém poderia sugerir algo que eu poderia tentar descobrir o que está acontecendo?
Desde já, obrigado.
EDITAR:
Outro exemplo
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)
Executar na cópia da tabela com índice de pontos
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
Executar na cópia da tabela sem índice de pontos
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 você pode ver, a consulta é significativamente mais lenta quando o índice de pontos está sendo usado.
EDIT 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');
EXPLICAR
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
fonte
Respostas:
Forçar efetivamente o planejador a fazer o que você deseja pode ajudar. Nesse caso, sub-define a tabela de polígonos antes de executar a junção espacial com a tabela de pontos. Você pode superar o planejador usando a sintaxe "WITH":
O problema ao tentar jogar esses jogos é que você está codificando em sua declaração a suposição "minha lista de polígonos será sempre mais seletiva do que minhas outras partes da consulta". O que pode não ser verdadeiro para todas as parametrizações da sua consulta ou para todos os aplicativos de uma consulta específica em um conjunto de dados distribuído heterogeneamente.
Mas pode funcionar.
UPDATE : Isso vai ainda mais longe no caminho perigoso de supor que você conhece a seletividade de suas cláusulas de antemão, desta vez também pegamos a seleção de atributos na tabela de pontos e a fazemos separadamente antes da junção espacial:
fonte
Observando a explicação para a execução na cópia da tabela com índice de pontos na primeira edição, parece que você está perdendo esse índice na tabela sem índice de pontos:
Você pode confirmar que o índice está lá?
- EDITAR -
Depois de estudar mais a sua pergunta (não é fácil, aliás), tenho as seguintes sugestões a fazer.
agrupar a tabela novamente com o índice "data_readingDatetime_index". O armazenamento em cluster é mais eficaz com consultas baseadas em intervalo. Parece que você não consulta o conjunto de dados com condições baseadas em intervalo
Execute o armazenamento em cluster real. O comando no item anterior não agrupa sua tabela, apenas expressa seu desejo de que, se a tabela fosse agrupada, você desejasse que ela fosse agrupada nesse índice. Agrupe-o com:
analise a tabela depois de agrupá-la para que as estatísticas (usadas pelo planejador para decidir qual estratégia escolher) anote o novo layout no disco:
Agora, como os dados são organizados em relação ao período de leitura e data, o planejador favorece uma estratégia na qual o índice data_readingDatetime_index é usado e, uma vez que sempre que usado, o plano de explicação parece ser o mais rápido, então talvez o desempenho melhore e flutue menos
Como eu disse no comentário à resposta de Paul acima, não pense que o planejador não mudará de estratégia dependendo dos filtros (mesmo que os filtros sejam sempre os mesmos e apenas seus valores mudem).
Há um exemplo no manual de alto desempenho do PostregSQL 9.0, altamente recomendado, no qual alterar uma condição de select ... da tabela t em que v <5 a v <6 alternou o plano de varredura de índice para varredura de tabela completa.
fonte