Limitar filas a través de la función espacial

9

Estoy tratando de mejorar el rendimiento de la consulta a continuación. No importa cómo escriba la consulta (subconsulta en la cláusula FROM, subconsulta en la cláusula WHERE) postgres insiste en ejecutar todas las ~ 570K filas a través de la costosa función ST_DWITHIN, aunque solo hay 60 filas donde county = 24. ¿Cómo puedo hacer que Postgres filtre en condado = 24 ANTES de ejecutar el postgis func que me parece que sería mucho más rápido y mucho más eficiente? 700 ms no es motivo de demasiada preocupación, pero a medida que esta tabla crece a 10M +, me preocupa el rendimiento.

También para tener en cuenta, p.id es una clave primaria, p.zipcode es un índice fk, z.county es un índice fk y p.geom tiene un índice GiST.

Consulta:

EXPLAIN ANALYZE
  SELECT count(p.id)
  FROM point AS p
  LEFT JOIN zipcode AS z
    ON p.zipcode = z.zipcode
  WHERE z.county = 24
    AND ST_DWithin(
      p.geom, 
      ST_SetSRID(ST_Point(-121.479756008715,38.563236291512),4269), 
      16090.0,
      false
    )

EXPLIQUE ANALIZAR:

Aggregate  (cost=250851.91..250851.92 rows=1 width=4) (actual time=724.007..724.007 rows=1 loops=1)
  ->  Hash Join  (cost=152.05..250851.34 rows=228 width=4) (actual time=0.359..723.996 rows=51 loops=1)
        Hash Cond: ((p.zipcode)::text = (z.zipcode)::text)
        ->  Seq Scan on point p  (cost=0.00..250669.12 rows=7437 width=10) (actual time=0.258..723.867 rows=63 loops=1)
              Filter: (((geom)::geography && '0101000020AD10000063DF8B52B45E5EC070FB752018484340'::geography) AND ('0101000020AD10000063DF8B52B45E5EC070FB752018484340'::geography && _st_expand((geom)::geography, 16090::double precision)) AND _st_dwithin((g (...)
              Rows Removed by Filter: 557731
        ->  Hash  (cost=151.38..151.38 rows=54 width=6) (actual time=0.095..0.095 rows=54 loops=1)
              Buckets: 1024  Batches: 1  Memory Usage: 3kB
              ->  Bitmap Heap Scan on zipcode z  (cost=4.70..151.38 rows=54 width=6) (actual time=0.023..0.079 rows=54 loops=1)
                    Recheck Cond: (county = 24)
                    Heap Blocks: exact=39
                    ->  Bitmap Index Scan on fki_zipcode_county_foreign_key  (cost=0.00..4.68 rows=54 width=0) (actual time=0.016..0.016 rows=54 loops=1)
                          Index Cond: (county = 24)
Planning time: 0.504 ms
Execution time: 724.064 ms
Josh
fuente
Tal vez intente cambiar la línea "apuntar como p izquierda unirse al código postal como z" a algo así como "apuntar como p izquierda unirse (SELECCIONAR * DESDE zipcode DONDE zipcode.county = 24) como z"?
weiji14
Solo lo intenté, los mismos resultados. Cuando copio las ~ 60 pointfilas donde county = 24 en una nueva tabla por sí mismas, la consulta solo toma .453 ms en comparación con 724, por lo que definitivamente hay una gran diferencia.
Josh
1
Debes usarlo count(*)como una cuestión de estilo. Si ides un pkid como dices, es lo NOT NULLque significa que son lo mismo. Excepto count(id)tiene el inconveniente de que tiene que hacer esa pregunta si ides anulable.
Evan Carroll
1
¿Puedo preguntar por qué estás usando una combinación externa izquierda? Intente cambiarlo a una unión interna ... Los resultados deberían ser idénticos
MickyT
Si z.country es el factor limitante, sugeriría que coloque esto primero en una consulta CTE y luego simplemente verifique esos resultados para una intersección con su punto de interés. Como el índice espacial es probablemente menos selectivo que condado = 24 en este caso, solo se interpone en el camino.
John Powell

Respuestas:

3

Puede ver el problema con los recuentos de filas esperados vs reales. El planificador piensa que hay 7.437 filas, sin embargo, solo hay 63. Las estadísticas están apagadas. Curiosamente, no está utilizando una búsqueda de índice (índice) de cuadro delimitador con el que DWithinpuede pegar el resultado \d point. ¿Qué versión de PostGIS y PostgreSQL?

Intenta correr ANALYZE point. ¿Obtiene el mismo plan cuando sube la condición?

JOIN zipcode AS z
  ON p.zipcode = z.zipcode
  AND z.county = 24
Evan Carroll
fuente
Ejecuté el análisis y también probé la nueva condición AND en ON pero aún obtenía 700 ms de tiempo de ejecución. Esto es PGSQL 9.4 y PostGIS 2.2.
Josh
2

Como nota al margen, existe una posibilidad razonable de que este comportamiento se modifique en PostGIS 2.3.0 si desea llamarlo un error.

De los documentos en PostgreSQL

Un número positivo que proporciona el costo de ejecución estimado para la función, en unidades de cpu_operator_cost. Si la función devuelve un conjunto, este es el costo por fila devuelta. Si no se especifica el costo, se supone 1 unidad para lenguaje C y funciones internas, y 100 unidades para funciones en todos los demás lenguajes. Los valores más grandes hacen que el planificador intente evitar evaluar la función con más frecuencia de la necesaria.

Entonces, el costo predeterminado era 1 (muy barato). D_WithinUsar un índice GIST es muy barato. Pero, eso se incrementó a 100 (por proxy de lo interno _ST_DWithin).

Yo tampoco soy un gran admirador del método CTE. Los CTE son una valla de optimización. Por lo tanto, hacer esto de esta manera elimina un margen potencial para la optimización futura. Si los valores predeterminados correctos lo arreglan, prefiero actualizar. Al final del día, tenemos que hacer el trabajo y ese método claramente funciona para usted.

Evan Carroll
fuente
1

Gracias a la sugerencia de John Powell, revisé la consulta para poner la condición de limitación del condado en una consulta con / CTE y esto mejoró bastante el rendimiento a 222 ms frente a 700. Todavía estoy muy lejos de los .74 ms que obtengo cuando los datos están en su Mesa propia. Todavía no estoy seguro de por qué el planificador no limita el conjunto de datos antes de ejecutar una costosa función postgis, y tendré que probar con conjuntos de datos más grandes cuando los tenga, pero esto parece ser una solución a esta situación única por ahora.

with points as (
   select p.id, p.geom from point p inner join zipcode z
   on p.zipcode = z.zipcode
   where county = 24
   ) 


SELECT count(points.id)
FROM points
WHERE ST_DWITHIN(points.geom, (ST_SetSRID(ST_Point(-121.479756008715,38.563236291512),4269)), 16090.0, false)
Josh
fuente
1
Tendríamos que ver los tres planes de consulta y el esquema de la tabla (solicitado en mi punto de respuesta).
Evan Carroll
0

Debe crear un índice en zipcode(county, zipcode) , que debería darle un escaneo de índice solo en z.

También es posible que desee experimentar con btree_gistla extensión ya sea la creación de point(zipcode, geom)índice o point(geom, zipcode)e zipcode(zipcode, county)índice.

Jakub Kania
fuente