Mysql
 sql >> Base de données >  >> RDS >> Mysql

Quelle approche est la plus rapide pour obtenir tous les POI de MySQL/MariaDB avec PHP/Laravel

La formule que vous utilisez pour la distance n'a pas beaucoup d'importance. Ce qui importe beaucoup plus, c'est le nombre de lignes que vous devez lire, traiter et trier. Dans le meilleur des cas, vous pouvez utiliser un index pour une condition dans la clause WHERE afin de limiter le nombre de lignes traitées. Vous pouvez essayer de catégoriser vos emplacements - Mais cela dépend de la nature de vos données, si cela fonctionne bien. Vous auriez également besoin de savoir quelle "catégorie" utiliser. Une solution plus générale serait d'utiliser un INDICE SPATIAL et le ST_Within() fonction.

Faisons maintenant quelques tests..

Dans ma BD (MySQL 5.7.18) j'ai le tableau suivant :

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

Les données proviennent de Free World Cities Database et contient 3173958 (3.1M) lignes.

Notez que geoPoint est redondant et égal à POINT(longitude, latitude) .

Considérez que l'utilisateur se trouve quelque part à Londres

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

et vous voulez trouver l'emplacement le plus proche des cities tableau.

Une requête "triviale" serait

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

Le résultat est

988204 Blackwall 1085.8212159861014

Temps d'exécution :~ 4.970 sec

Si vous utilisez la fonction moins complexe ST_Distance() , vous obtenez le même résultat avec un temps d'exécution de ~ 4.580 sec - ce qui n'est pas tellement différent.

Notez que vous n'avez pas besoin de stocker un point géographique dans le tableau. Vous pouvez aussi bien utiliser (point(c.longitude, c.latitude) au lieu de c.geoPoint . A ma grande surprise c'est encore plus rapide (~3.6 sec pour ST_Distance et ~4,0 s pour ST_Distance_Sphere ). Cela pourrait être encore plus rapide si je n'avais pas de geoPoint colonne du tout. Mais cela n'a pas beaucoup d'importance, puisque vous ne voulez pas que l'utilisateur attende, alors connectez-vous pour une réponse, si vous pouvez faire mieux.

Voyons maintenant comment nous pouvons utiliser l'INDICE SPATIAL avec ST_Within() .

Vous devez définir un polygone qui contiendra l'emplacement le plus proche. Un moyen simple consiste à utiliser ST_Buffer() qui va générer un polygone avec 32 points et est presque un cercle*.

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

Le résultat est le même. Le temps d'exécution est de ~ 0.000 sec (c'est ce que mon client (HeidiSQL ) dit).

* Notez que le @radius est noté en degrés et ainsi le polygone ressemblera plus à une ellipse qu'à un cercle. Mais dans mes tests j'ai toujours obtenu le même résultat qu'avec la solution simple et lente. J'étudierais cependant plus de cas extrêmes avant de l'utiliser dans mon code de production.

Vous devez maintenant trouver le rayon optimal pour votre application/vos données. S'il est trop petit, vous risquez de ne pas obtenir de résultats ou de manquer le point le plus proche. S'il est trop volumineux, vous devrez peut-être traiter trop de lignes.

Voici quelques chiffres pour le cas de test donné :

  • @radius =0.001 :Aucun résultat
  • @radius =0,01 :exactement un emplacement (un peu de chance) - Temps d'exécution ~ 0,000 s
  • @radius =0.1 :55 emplacements - Temps d'exécution ~ 0.000 sec
  • @radius =1.0 : 2 183 emplacements - Temps d'exécution ~ 0,030 s