La fórmula que uses para la distancia no importa mucho. Lo que importa mucho más es la cantidad de filas que debe leer, procesar y ordenar. En el mejor de los casos, puede usar un índice para una condición en la cláusula WHERE para limitar el número de filas procesadas. Puede intentar categorizar sus ubicaciones, pero depende de la naturaleza de sus datos, si eso va a funcionar bien. También necesitaría averiguar qué "categoría" usar. Una solución más general sería usar un ÍNDICE ESPACIAL y el ST_Within() función.
Ahora hagamos algunas pruebas..
En mi base de datos (MySQL 5.7.18) tengo la siguiente tabla:
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
Los datos provienen de Free World Cities Database y contiene 3173958 (3,1 millones) de filas.
Tenga en cuenta que geoPoint
es redundante e igual a POINT(longitude, latitude)
.
Considere que el usuario se encuentra en algún lugar de Londres
set @lon = 0.0;
set @lat = 51.5;
y desea encontrar la ubicación más cercana de las cities
mesa.
Una consulta "trivial" sería
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
order by dist
limit 1
El resultado es
988204 Blackwall 1085.8212159861014
Tiempo de ejecución:~ 4.970 seg
Si usa la función menos compleja ST_Distance()
, obtienes el mismo resultado con un tiempo de ejecución de ~ 4.580 segundos, lo que no es mucha diferencia.
Tenga en cuenta que no necesita almacenar un punto geográfico en la tabla. También puedes usar (point(c.longitude, c.latitude)
en lugar de c.geoPoint
. Para mi sorpresa, es incluso más rápido (~3,6 segundos para ST_Distance
y ~4,0 segundos para ST_Distance_Sphere
). Podría ser incluso más rápido si no tuviera un geoPoint
columna en absoluto. Pero eso aún no importa mucho, ya que no desea que el usuario espere, así que inicie sesión para obtener una respuesta, si puede hacerlo mejor.
Ahora veamos cómo podemos usar el ÍNDICE ESPACIAL con ST_Within()
.
Necesitas definir un polígono que contendrá la ubicación más cercana. Una forma sencilla es usar ST_Buffer() que generará un polígono de 32 puntos y es casi un círculo*.
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
El resultado es el mismo. El tiempo de ejecución es ~ 0.000 seg (eso es lo que mi cliente (HeidiSQL ) dice).
* Tenga en cuenta que @radius
se expresa en grados y, por lo tanto, el polígono se parecerá más a una elipse que a un círculo. Pero en mis pruebas siempre obtuve el mismo resultado que con la solución simple y lenta. Sin embargo, investigaría más casos extremos antes de usarlos en mi código de producción.
Ahora necesita encontrar el radio óptimo para su aplicación/datos. Si es demasiado pequeño, es posible que no obtenga resultados o que se pierda el punto más cercano. Si es demasiado grande, es posible que deba procesar demasiadas filas.
Aquí algunos números para el caso de prueba dado:
- @radius =0.001:Sin resultado
- @radius =0,01:exactamente una ubicación (un poco de suerte) - Tiempo de ejecución ~ 0,000 segundos
- @radius =0,1:55 ubicaciones - Tiempo de ejecución ~ 0,000 s
- @radius =1.0:2183 ubicaciones - Tiempo de ejecución ~ 0.030 segundos