Vilken formel du använder för avståndet spelar inte så stor roll. Det som betyder mycket mer är antalet rader som du måste läsa, bearbeta och sortera. I bästa fall kan du använda ett index för ett villkor i WHERE-satsen för att begränsa antalet bearbetade rader. Du kan försöka kategorisera dina platser - Men det beror på vilken typ av data du har, om det kommer att fungera bra. Du skulle också behöva ta reda på vilken "kategori" du ska använda. En mer allmän lösning skulle vara att använda ett SPATIAL INDEX och ST_Within() funktion.
Låt oss nu köra några tester...
I min DB (MySQL 5.7.18) har jag följande tabell:
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
Uppgifterna kommer från Free World Cities Database och innehåller 3173958 (3,1M) rader.
Observera att geoPoint
är redundant och lika med POINT(longitude, latitude)
.
Tänk på att användaren befinner sig någonstans i London
set @lon = 0.0;
set @lat = 51.5;
och du vill hitta den närmaste platsen från cities
bord.
En "trivial" fråga skulle vara
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
order by dist
limit 1
Resultatet är
988204 Blackwall 1085.8212159861014
Utförandetid:~ 4,970 sek
Om du använder den mindre komplexa funktionen ST_Distance()
, får du samma resultat med en exekveringstid på ~ 4,580 sek - vilket inte är så stor skillnad.
Observera att du inte behöver lagra en geopunkt i tabellen. Du kan lika gärna använda (point(c.longitude, c.latitude)
istället för c.geoPoint
. Till min förvåning är det ännu snabbare (~3,6 sek för ST_Distance
och ~4,0 sek för ST_Distance_Sphere
). Det kan vara ännu snabbare om jag inte hade en geoPoint
kolumn överhuvudtaget. Men det spelar ändå inte så stor roll, eftersom du inte vill att användaren ska vänta så logga in på ett svar, om du kan göra bättre.
Låt oss nu titta på hur vi kan använda SPATIAL INDEX med ST_Within()
.
Du måste definiera en polygon som kommer att innehålla närmaste plats. Ett enkelt sätt är att använda ST_Buffer() som kommer att generera en polygon med 32 punkter och är nästan en cirkel*.
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
Resultatet är detsamma. Körningstiden är ~ 0,000 sek (det är vad min klient (HeidiSQL ) säger).
* Observera att @radius
noteras i grader och därmed kommer polygonen att vara mer som en ellips snarare än en cirkel. Men i mina tester fick jag alltid samma resultat som med den enkla och långsamma lösningen. Jag skulle dock undersöka fler kantfall innan jag använder det i min produktionskod.
Nu behöver du hitta den optimala radien för din applikation/data. Om den är för liten - kanske du inte får några resultat, eller missar närmaste punkt. Om den är för stor - kan du behöva bearbeta för många rader.
Här några siffror för det givna testfallet:
- @radius =0,001:Inget resultat
- @radius =0,01:exakt en plats (typ av tur) - Exekveringstid ~ 0,000 sek
- @radius =0,1:55 platser - Exekveringstid ~ 0,000 sek
- @radius =1,0:2183 platser - Exekveringstid ~ 0,030 sek