Nezáleží na tom, jaký vzorec pro vzdálenost použijete. Mnohem důležitější je počet řádků, které musíte číst, zpracovávat a třídit. V nejlepším případě můžete použít index pro podmínku v klauzuli WHERE, abyste omezili počet zpracovaných řádků. Můžete se pokusit kategorizovat svá místa – ale záleží na povaze vašich dat, jestli to bude fungovat dobře. Také byste museli zjistit, jakou "kategorii" použít. Obecnějším řešením by bylo použít SPATIAL INDEX a ST_Within() funkce.
Nyní provedeme nějaké testy..
V mé DB (MySQL 5.7.18) mám následující tabulku:
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
Data pocházejí z Databáze svobodných světových měst a obsahuje 3173958 (3,1M) řádků.
Všimněte si, že geoPoint
je redundantní a rovná se POINT(longitude, latitude)
.
Berte, že uživatel se nachází někde v Londýně
set @lon = 0.0;
set @lat = 51.5;
a chcete najít nejbližší umístění z cities
tabulka.
"triviální" dotaz by byl
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
order by dist
limit 1
Výsledkem je
988204 Blackwall 1085.8212159861014
Doba provedení:~ 4,970 s
Pokud použijete méně komplexní funkci ST_Distance()
, získáte stejný výsledek s dobou provedení ~ 4,580 s - což není tak velký rozdíl.
Všimněte si, že nemusíte ukládat geografický bod do tabulky. Můžete stejně dobře použít (point(c.longitude, c.latitude)
místo c.geoPoint
. K mému překvapení je ještě rychlejší (~3,6 s pro ST_Distance
a ~4,0 s pro ST_Distance_Sphere
). Mohlo by to být ještě rychlejší, kdybych neměl geoPoint
sloupec vůbec. Ale na tom stejně moc nezáleží, protože nechcete, aby uživatel čekal, tak se přihlaste na odpověď, pokud můžete udělat lépe.
Nyní se podívejme, jak můžeme použít SPATIAL INDEX pomocí ST_Within()
.
Musíte definovat polygon který bude obsahovat nejbližší umístění. Jednoduchým způsobem je použít ST_Buffer() který vytvoří mnohoúhelník s 32 body a je téměř kruhem*.
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
Výsledek je stejný. Doba provedení je ~ 0,000 s (to je to, co můj klient (HeidiSQL ) říká).
* Všimněte si, že @radius
je označen ve stupních, a proto bude mnohoúhelník připomínat spíše elipsu než kružnici. Ale v mých testech jsem vždy dostal stejný výsledek jako s jednoduchým a pomalým řešením. Než to použiji ve svém produkčním kódu, prozkoumal bych více okrajových případů.
Nyní musíte najít optimální rádius pro vaši aplikaci/data. Pokud je příliš malý, můžete získat žádné výsledky nebo přehlédnout nejbližší bod. Pokud je příliš velký, možná budete muset zpracovat příliš mnoho řádků.
Zde jsou některá čísla pro daný testovací případ:
- @radius =0,001:Žádný výsledek
- @radius =0,01:přesně jedno místo (druh štěstí) – doba provedení ~ 0,000 s
- @radius =0,1:55 míst – doba provedení ~ 0,000 s
- @radius =1,0:2183 umístění – doba provedení ~ 0,030 s