Rumus mana yang Anda gunakan untuk jarak tidak terlalu menjadi masalah. Yang lebih penting adalah jumlah baris yang harus Anda baca, proses, dan urutkan. Dalam kasus terbaik, Anda dapat menggunakan indeks untuk kondisi dalam klausa WHERE untuk membatasi jumlah baris yang diproses. Anda dapat mencoba untuk mengkategorikan lokasi Anda - Tapi itu tergantung pada sifat data Anda, jika itu akan bekerja dengan baik. Anda juga perlu mencari tahu "kategori" mana yang akan digunakan. Solusi yang lebih umum adalah dengan menggunakan SPATIAL INDEX dan ST_Within() fungsi.
Sekarang mari kita jalankan beberapa tes..
Di DB saya (MySQL 5.7.18) saya memiliki tabel berikut:
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
Data berasal dari Free World Cities Database dan berisi 3173958 (3.1M) baris.
Perhatikan bahwa geoPoint
berlebihan dan sama dengan POINT(longitude, latitude)
.
Pertimbangkan pengguna berada di suatu tempat di London
set @lon = 0.0;
set @lat = 51.5;
dan Anda ingin mencari lokasi terdekat dari cities
tabel.
Kueri "sepele" adalah
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
order by dist
limit 1
Hasilnya adalah
988204 Blackwall 1085.8212159861014
Waktu eksekusi:~ 4,970 detik
Jika Anda menggunakan fungsi yang tidak terlalu rumit ST_Distance()
, Anda mendapatkan hasil yang sama dengan waktu eksekusi ~ 4,580 detik - perbedaan yang tidak terlalu jauh.
Perhatikan bahwa Anda tidak perlu menyimpan titik geografis dalam tabel. Anda juga dapat menggunakan (point(c.longitude, c.latitude)
bukannya c.geoPoint
. Yang mengejutkan saya, ini bahkan lebih cepat (~3,6 detik untuk ST_Distance
dan ~4,0 detik untuk ST_Distance_Sphere
). Mungkin akan lebih cepat jika saya tidak memiliki geoPoint
kolom sama sekali. Tapi itu tidak terlalu menjadi masalah, karena Anda tidak ingin pengguna menunggu, jadi log untuk istirahat, jika Anda bisa melakukannya dengan lebih baik.
Sekarang mari kita lihat bagaimana kita dapat menggunakan SPATIAL INDEX dengan ST_Within()
.
Anda perlu mendefinisikan poligon yang akan berisi lokasi terdekat. Cara sederhana adalah dengan menggunakan ST_Buffer() yang akan menghasilkan poligon dengan 32 titik dan hampir berbentuk lingkaran*.
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
Hasilnya sama. Waktu eksekusi adalah ~ 0,000 detik (itulah yang dilakukan klien saya (HeidiSQL ) mengatakan).
* Perhatikan bahwa @radius
dinotasikan dalam derajat dan dengan demikian poligon akan lebih seperti elips daripada lingkaran. Tetapi dalam pengujian saya, saya selalu mendapatkan hasil yang sama dengan solusi sederhana dan lambat. Saya akan menyelidiki lebih banyak kasus Edge, sebelum saya menggunakannya dalam kode produksi saya.
Sekarang Anda perlu menemukan radius optimal untuk aplikasi/data Anda. Jika terlalu kecil - Anda mungkin tidak mendapatkan hasil, atau kehilangan titik terdekat. Jika terlalu besar - Anda mungkin perlu memproses terlalu banyak baris.
Berikut beberapa angka untuk kasus uji yang diberikan:
- @radius =0,001:Tidak ada hasil
- @radius =0,01:tepat satu lokasi (beruntung) - Waktu eksekusi ~ 0,000 detik
- @radius =0,1:55 lokasi - Waktu eksekusi ~ 0,000 detik
- @radius =1,0:2183 lokasi - Waktu eksekusi ~ 0,030 detik