Welke formule je voor de afstand gebruikt maakt niet zoveel uit. Veel belangrijker is het aantal rijen dat je moet lezen, verwerken en sorteren. In het beste geval kunt u een index gebruiken voor een voorwaarde in de WHERE-component om het aantal verwerkte rijen te beperken. U kunt proberen uw locaties te categoriseren - maar het hangt af van de aard van uw gegevens of dat goed gaat werken. U moet ook weten welke "categorie" u moet gebruiken. Een meer algemene oplossing zou zijn om een SPATIAL INDEX . te gebruiken en de ST_Within() functie.
Laten we nu wat tests uitvoeren.
In mijn DB (MySQL 5.7.18) heb ik de volgende tabel:
CREATE TABLE `cities` (
`cityId` MEDIUMINT(9) UNSIGNED NOT NULL AUTO_INCREMENT,
`country` CHAR(2) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`city` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`accentCity` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`region` CHAR(2) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`population` INT(10) UNSIGNED NULL DEFAULT NULL,
`latitude` DECIMAL(10,7) NOT NULL,
`longitude` DECIMAL(10,7) NOT NULL,
`geoPoint` POINT NOT NULL,
PRIMARY KEY (`cityId`),
SPATIAL INDEX `geoPoint` (`geoPoint`)
) COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB
De gegevens zijn afkomstig van Free World Cities Database en bevat 3173958 (3,1 miljoen) rijen.
Merk op dat geoPoint
is redundant en gelijk aan POINT(longitude, latitude)
.
Bedenk dat de gebruiker zich ergens in Londen bevindt
set @lon = 0.0;
set @lat = 51.5;
en je wilt de dichtstbijzijnde locatie vinden van de cities
tafel.
Een "triviale" vraag zou zijn
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
order by dist
limit 1
Het resultaat is
988204 Blackwall 1085.8212159861014
Uitvoeringstijd:~ 4.970 sec
Als je de minder complexe functie ST_Distance()
. gebruikt , krijg je hetzelfde resultaat met een uitvoeringstijd van ~ 4.580 sec - wat niet zo veel verschil is.
Merk op dat u geen geopunt in de tabel hoeft op te slaan. U kunt net zo goed gebruik maken van (point(c.longitude, c.latitude)
in plaats van c.geoPoint
. Tot mijn verbazing is het zelfs nog sneller (~3,6 sec voor ST_Distance
en ~4,0 sec voor ST_Distance_Sphere
). Het zou nog sneller zijn als ik geen geoPoint
. had kolom helemaal niet. Maar dat maakt nog steeds niet zoveel uit, omdat je niet wilt dat de gebruiker wacht, dus log in op een reactie, als je het beter kunt doen.
Laten we nu eens kijken hoe we de SPATIAL INDEX . kunnen gebruiken met ST_Within()
.
U moet een veelhoek definiëren die de dichtstbijzijnde locatie zal bevatten. Een eenvoudige manier is om ST_Buffer() . te gebruiken die een polygoon met 32 punten genereert en bijna een cirkel* is.
set @point = point(@lon, @lat);
set @radius = 0.1;
set @polygon = ST_Buffer(@point, @radius);
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
where st_within(c.geoPoint, @polygon)
order by dist
limit 1
Het resultaat is hetzelfde. De uitvoeringstijd is ~ 0.000 sec (dat is wat mijn cliënt (HeidiSQL ) zegt).
* Merk op dat de @radius
wordt genoteerd in graden en dus zal de veelhoek meer op een ellips dan op een cirkel lijken. Maar in mijn tests kreeg ik altijd hetzelfde resultaat als met de eenvoudige en langzame oplossing. Ik zou echter meer randgevallen onderzoeken voordat ik het in mijn productiecode gebruik.
Nu moet u de optimale straal voor uw toepassing/gegevens vinden. Als het te klein is, krijgt u mogelijk geen resultaten of mist u het dichtstbijzijnde punt. Als het te groot is, moet u mogelijk te veel rijen verwerken.
Hier enkele cijfers voor de gegeven testcase:
- @radius =0.001:Geen resultaat
- @radius =0.01:precies één locatie (soort van geluk) - Uitvoeringstijd ~ 0.000 sec
- @radius =0.1:55 locaties - Uitvoeringstijd ~ 0.000 sec
- @radius =1,0:2183 locaties - Uitvoeringstijd ~ 0,030 sec