sql >> Database teknologi >  >> RDS >> Mysql

Hvilken tilgang er hurtigere til at få alle POI'er fra MySQL/MariaDB med PHP/Laravel

Hvilken formel du bruger for afstanden betyder ikke så meget. Det, der betyder meget mere, er antallet af rækker, som du skal læse, behandle og sortere. I bedste tilfælde kan du bruge et indeks for en betingelse i WHERE-sætningen for at begrænse antallet af behandlede rækker. Du kan prøve at kategorisere dine lokationer - Men det afhænger af arten af ​​dine data, om det kommer til at fungere godt. Du skal også finde ud af, hvilken "kategori" du skal bruge. En mere generel løsning ville være at bruge et SPATIAL INDEX og ST_Within() funktion.

Lad os nu køre nogle tests...

I min DB (MySQL 5.7.18) har jeg følgende 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

Dataene kommer fra Free World Cities Database og indeholder 3173958 (3,1M) rækker.

Bemærk, at geoPoint er redundant og lig med POINT(longitude, latitude) .

Overvej, at brugeren befinder sig et sted i London

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

og du vil finde den nærmeste placering fra cities tabel.

En "triviel" forespørgsel ville være

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

Resultatet er

988204 Blackwall 1085.8212159861014

Udførelsestid:~ 4.970 sek.

Hvis du bruger den mindre komplekse funktion ST_Distance() , får du det samme resultat med en eksekveringstid på ~ 4.580 sek. - hvilket ikke er så stor forskel.

Bemærk, at du ikke behøver at gemme et geopunkt i tabellen. Du kan lige så godt bruge (point(c.longitude, c.latitude) i stedet for c.geoPoint . Til min overraskelse er det endnu hurtigere (~3,6 sek for ST_Distance og ~4,0 sek. for ST_Distance_Sphere ). Det kunne være endnu hurtigere, hvis jeg ikke havde et geoPoint kolonne overhovedet. Men det betyder stadig ikke så meget, da du ikke ønsker, at brugeren skal vente, så log på et svar, hvis du kan gøre det bedre.

Lad os nu se, hvordan vi kan bruge SPATIAL INDEX med ST_Within() .

Du skal definere en polygon som vil indeholde den nærmeste placering. En enkel måde er at bruge ST_Buffer() som vil generere en polygon med 32 punkter og er næsten 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 er det samme. Udførelsestiden er ~ 0.000 sek (det er hvad min klient (HeidiSQL ) siger).

* Bemærk, at @radius er noteret i grader og dermed vil polygonen være mere som en ellipse end en cirkel. Men i mine test fik jeg altid det samme resultat som med den enkle og langsomme løsning. Jeg ville dog undersøge flere edge cases, før jeg bruger det i min produktionskode.

Nu skal du finde den optimale radius til din applikation/data. Hvis den er for lille - får du muligvis ingen resultater eller går glip af det nærmeste punkt. Hvis den er for stor - skal du muligvis behandle for mange rækker.

Her nogle tal for den givne testcase:

  • @radius =0,001:Intet resultat
  • @radius =0,01:præcis én lokation (en slags heldig) - Eksekveringstid ~ 0,000 sek.
  • @radius =0,1:55 lokationer - Eksekveringstid ~ 0,000 sek.
  • @radius =1,0:2183 lokationer - Eksekveringstid ~ 0,030 sek.


  1. Sådan tjekker du PostgreSQL-versionen

  2. Hvordan skriver jeg data fra R til PostgreSQL-tabeller med en autoinkrementerende primærnøgle?

  3. Opdel værdier over flere rækker

  4. MAX(kolonne) giver mig en forkert værdi