Es gibt zwei Schlüssel, um eine gute geodätische Abfrageleistung bei großen Tabellen mit geometry
Spalten zu erzielen, die geografische WGS 1984-Daten (SRID 4326) verwenden:
- Verwenden Sie die
ST_DWithin
Funktion, die anhand eines verfügbaren räumlichen Index sucht und Geografie-Features mit einer kartesischen Entfernung findet
- Erstellen Sie einen zusätzlichen Index für die geografische Besetzung, damit Sie
ST_DWithin
ihn verwenden können
Schauen wir uns also an, was in der realen Welt passiert. Zuerst müssen wir eine Tabelle mit einer Million zufälliger Punkte erstellen und füllen:
DROP TABLE IF EXISTS example1
;
CREATE TABLE example1 (
idcol serial NOT NULL,
geomcol geometry NULL,
CONSTRAINT example1_pk PRIMARY KEY (idcol),
CONSTRAINT enforce_srid CHECK (st_srid(geomcol) = 4326)
)
with (
OIDS=FALSE
);
INSERT INTO example1(geomcol)
SELECT ST_SetSRID(
ST_MakePoint(
(random()*360.0) - 180.0,
(acos(1.0 - 2.0 * random()) * 2.0 - pi()) * 90.0 / pi()),
4326) as geomcol
FROM generate_series(1, 1000000) vtab;
CREATE INDEX example1_spx ON example1 USING GIST (geomcol);
-- (took about 22 sec)
Wenn wir die ST_Distance-Abfrage ausführen, erhalten wir Ihren erwarteten vollständigen Tabellenscan:
EXPLAIN ANALYZE VERBOSE
SELECT count(*)
FROM example1
WHERE ST_Distance(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography) < 30 * 1609.34
;
Aggregate (cost=274167.33..274167.34 rows=1 width=0) (actual time=4940.531..4940.532 rows=1 loops=1)
Output: count(*)
-> Seq Scan on bob.example1 (cost=0.00..273334.00 rows=333333 width=0) (actual time=592.766..4940.509 rows=11 loops=1)
Output: idcol, geomcol
Filter: (_st_distance((example1.geomcol)::geography, '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography, 0::double precision, true) < 48280.2::double precision)
Rows Removed by Filter: 999989
Planning time: 2.137 ms
Execution time: 4940.568 ms
Wenn wir jetzt verwenden ST_DWithin
, erhalten wir immer noch einen vollständigen Tabellenscan (wenn auch einen schnelleren):
EXPLAIN ANALYZE VERBOSE
SELECT count(*)
FROM example1
WHERE ST_DWithin(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography,30 * 1609.34)
;
Aggregate (cost=405867.33..405867.34 rows=1 width=0) (actual time=908.716..908.716 rows=1 loops=1)
Output: count(*)
-> Seq Scan on bob.example1 (cost=0.00..405834.00 rows=13333 width=0) (actual time=38.449..908.700 rows=7 loops=1)
Output: idcol, geomcol
Filter: (((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography) AND ('0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography && _st_expand((example1.geomcol)::geography, 48280.2::double precision) (...)
Rows Removed by Filter: 999993
Planning time: 2.017 ms
Execution time: 908.763 ms
Und dies ist das letzte Stück - Aufbau des Deckungsindex (Besetzungsgeographie):
CREATE INDEX example1_gpx ON example1 USING GIST (geography(geomcol));
-- (Takes an extra 13 sec)
EXPLAIN ANALYZE VERBOSE
SELECT count(*)
FROM example1
WHERE ST_DWithin(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography,30 * 1609.34)
;
Aggregate (cost=96538.95..96538.96 rows=1 width=0) (actual time=0.775..0.775 rows=1 loops=1)
Output: count(*)
-> Bitmap Heap Scan on bob.example1 (cost=8671.62..96505.62 rows=13333 width=0) (actual time=0.586..0.769 rows=19 loops=1)
Output: idcol, geomcol
Recheck Cond: ((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography)
Filter: (('0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography && _st_expand((example1.geomcol)::geography, 48280.2::double precision)) AND _st_dwithin((example1.geomcol)::geography, '0101000020E61000005D6DC5FEB2BB1B40545227A089684740':: (...)
Rows Removed by Filter: 14
Heap Blocks: exact=33
-> Bitmap Index Scan on example1_gpx (cost=0.00..8668.29 rows=200000 width=0) (actual time=0.384..0.384 rows=33 loops=1)
Index Cond: ((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography)
Planning time: 2.572 ms
Execution time: 0.820 ms
Schließlich verwendet der Optimierer den räumlichen Index und es wird angezeigt, aber was sind drei Größenordnungen zwischen Freunden?
Einige Einschränkungen:
Da ich ein Datenbank-Nerd bin, verfügt mein Heim-PC über 16 GB RAM, sechs 3,3-GHz-Kerne und eine 256-Gbit-SSD für den Standardtabellenbereich der Datenbank. Ihr Kilometerstand kann variieren
Ich habe die Erstellungs-SQL vor jeder Abfrage erneut ausgeführt, um das Spielfeld in Bezug auf "heiße" Seiten im Cache auszugleichen. Dies kann jedoch zu geringfügig unterschiedlichen Ergebnissen führen, da derselbe zufällige Startwert nicht für verschiedene Läufe verwendet wurde
Und ein Hinweis:
- Ich habe den ursprünglichen Breitengradbereich {-90, + 90} optimiert, um den Arcus-Cosinus für eine flächengleiche Verteilung zu verwenden (weniger auf die Pole ausgerichtet).
ST_SetSRID()
um dieST_MakePoint
zuvor in der Abfrage Geographie Gießen.