Коя формула използвате за разстоянието няма голямо значение. Много по-важен е броят на редовете, които трябва да прочетете, обработите и сортирате. В най-добрия случай можете да използвате индекс за условие в клаузата WHERE, за да ограничите броя на обработените редове. Можете да опитате да категоризирате местоположенията си - но това зависи от естеството на вашите данни, дали това ще работи добре. Вие също ще трябва да разберете коя "категория" да използвате. По-общо решение би било използването на ПРОСТРАНИЧЕН ИНДЕКС и ST_Within() функция.
Сега нека проведем някои тестове...
В моята DB (MySQL 5.7.18) имам следната таблица:
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
Данните идват от Безплатна база данни за световни градове и съдържа 3173958 (3,1M) реда.
Имайте предвид, че geoPoint
е излишен и е равен на POINT(longitude, latitude)
.
Считайте, че потребителят се намира някъде в Лондон
set @lon = 0.0;
set @lat = 51.5;
и искате да намерите най-близкото местоположение от cities
таблица.
Една "тривиална" заявка би била
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
order by dist
limit 1
Резултатът е
988204 Blackwall 1085.8212159861014
Време за изпълнение:~ 4,970 сек
Ако използвате по-малко сложната функция ST_Distance()
, получавате същия резултат с време на изпълнение от ~ 4,580 сек – което не е толкова голяма разлика.
Имайте предвид, че не е необходимо да съхранявате гео точка в таблицата. Можете също да използвате (point(c.longitude, c.latitude)
). вместо c.geoPoint
. За моя изненада е дори по-бърз (~3,6 секунди за ST_Distance
и ~4.0 сек за ST_Distance_Sphere
). Може да е дори по-бързо, ако нямах geoPoint
колона изобщо. Но това все още няма голямо значение, тъй като не искате потребителят да чака, така че влезте за повторно време, ако можете да направите по-добре.
Сега нека разгледаме как можем да използваме ПРОСТРАНИЧЕН ИНДЕКС с ST_Within()
.
Трябва да дефинирате многоъгълник който ще съдържа най-близкото местоположение. Един прост начин е да използвате ST_Buffer() който ще генерира многоъгълник с 32 точки и е почти кръг*.
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
Резултатът е същият. Времето за изпълнение е ~ 0,000 сек (това е, което моят клиент (HeidiSQL ) казва).
* Имайте предвид, че @radius
се отбелязва в градуси и по този начин многоъгълникът ще прилича повече на елипса, отколкото на кръг. Но в моите тестове винаги получавах същия резултат като при простото и бавно решение. Въпреки това бих проучил повече крайни случаи, преди да го използвам в производствения си код.
Сега трябва да намерите оптималния радиус за вашето приложение/данни. Ако е твърде малък - може да нямате резултати или да пропуснете най-близката точка. Ако е твърде голям - може да се наложи да обработите твърде много редове.
Ето някои числа за дадения тестов случай:
- @radius =0,001:Няма резултат
- @radius =0,01:точно едно местоположение (вид късмет) – Време за изпълнение ~ 0,000 сек.
- @radius =0,1:55 местоположения – Време за изпълнение ~ 0,000 сек.
- @radius =1.0:2183 местоположения – Време за изпълнение ~ 0,030 сек.