Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Które podejście jest szybsze w celu uzyskania wszystkich punktów POI z MySQL/MariaDB z PHP/Laravel?

Który wzór na odległość nie ma większego znaczenia. Dużo bardziej liczy się liczba wierszy, które musisz przeczytać, przetworzyć i posortować. W najlepszym przypadku możesz użyć indeksu dla warunku w klauzuli WHERE, aby ograniczyć liczbę przetwarzanych wierszy. Możesz spróbować skategoryzować swoje lokalizacje - ale to zależy od charakteru Twoich danych, czy to ma działać dobrze. Musisz również dowiedzieć się, której „kategorii” użyć. Bardziej ogólnym rozwiązaniem byłoby użycie INDEKSU PRZESTRZENNEGO i ST_Within() funkcja.

Przeprowadźmy teraz kilka testów.

W mojej bazie danych (MySQL 5.7.18) mam następującą 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

Dane pochodzą z Free World Cities Database i zawiera 3173958 (3,1 mln) wierszy.

Zwróć uwagę, że geoPoint jest nadmiarowy i równy POINT(longitude, latitude) .

Uważam, że użytkownik znajduje się gdzieś w Londynie

set @lon = 0.0;
set @lat = 51.5;

i chcesz znaleźć najbliższą lokalizację z cities tabela.

„Trywialne” zapytanie to

select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
order by dist
limit 1

Wynik to

988204 Blackwall 1085.8212159861014

Czas wykonania:~ 4,970 s

Jeśli używasz mniej złożonej funkcji ST_Distance() , otrzymujesz ten sam wynik z czasem wykonania ~ 4,580 s - co nie jest tak dużą różnicą.

Pamiętaj, że nie musisz przechowywać punktu geograficznego w tabeli. Możesz równie dobrze użyć (point(c.longitude, c.latitude) zamiast c.geoPoint . Ku mojemu zdziwieniu jest jeszcze szybszy (~3,6 s dla ST_Distance i ~4.0 s dla ST_Distance_Sphere ). Mogłoby być jeszcze szybciej, gdybym nie miał geoPoint w ogóle kolumna. Ale to nadal nie ma większego znaczenia, ponieważ nie chcesz, aby użytkownik czekał, więc zaloguj się na odpowiedź, jeśli możesz zrobić to lepiej.

Zobaczmy teraz, jak możemy użyć INDEKSU PRZESTRZENNEGO z ST_Within() .

Musisz zdefiniować wielokąt który będzie zawierał najbliższą lokalizację. Prostym sposobem jest użycie ST_Buffer() który wygeneruje wielokąt z 32 punktami i jest prawie okręgiem*.

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

Wynik jest taki sam. Czas wykonania wynosi ~0.000 s (to właśnie mój klient (HeidiSQL) ) mówi).

* Zwróć uwagę, że @radius jest zapisany w stopniach, a zatem wielokąt będzie bardziej przypominał elipsę niż okrąg. Ale w moich testach zawsze uzyskiwałem ten sam wynik, co w przypadku prostego i wolnego rozwiązania. Chciałbym jednak zbadać więcej przypadków brzegowych, zanim użyję go w moim kodzie produkcyjnym.

Teraz musisz znaleźć optymalny promień dla swojej aplikacji/danych. Jeśli jest za mały - możesz nie uzyskać wyników lub przegapić najbliższy punkt. Jeśli jest za duży — może być konieczne przetworzenie zbyt wielu wierszy.

Oto kilka liczb dla danego przypadku testowego:

  • @promień =0,001:Brak wyniku
  • @radius =0,01:dokładnie jedna lokalizacja (trochę szczęścia) - czas wykonania ~ 0,000 s
  • @radius =0,1:55 lokalizacji – czas wykonania ~ 0,000 s
  • @radius =1.0:2183 lokalizacje – czas wykonania ~ 0,030 s


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Zainstaluj moduł mysql dla Pythona z pip

  2. Najłatwiejszy sposób na link weryfikacyjny e-mail PHP

  3. Jak mogę użyć mysqli_fetch_array() dwa razy?

  4. php losowe dane mysql

  5. Jak pominąć puste dane w MySQL?