Wenn Sie der Koordinatentabelle Hilfsfelder hinzufügen, können Sie die Antwortzeit der Abfrage verbessern.
So was:
CREATE TABLE `Coordinates` (
`id` INT(10) UNSIGNED NOT NULL COMMENT 'id for the object',
`type` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'type',
`sin_lat` FLOAT NOT NULL COMMENT 'sin(lat) in radians',
`cos_cos` FLOAT NOT NULL COMMENT 'cos(lat)*cos(lon) in radians',
`cos_sin` FLOAT NOT NULL COMMENT 'cos(lat)*sin(lon) in radians',
`lat` FLOAT NOT NULL COMMENT 'latitude in degrees',
`lon` FLOAT NOT NULL COMMENT 'longitude in degrees',
INDEX `lat_lon_idx` (`lat`, `lon`)
)
Wenn Sie TokuDB verwenden, erzielen Sie eine noch bessere Leistung, wenn Sie Clustering-Indizes für eines der Prädikate hinzufügen, z. B.:
alter table Coordinates add clustering index c_lat(lat);
alter table Coordinates add clustering index c_lon(lon);
Sie benötigen für jeden Punkt das Basislat und Lon in Grad sowie sin (lat) im Bogenmaß, cos (lat) * cos (lon) im Bogenmaß und cos (lat) * sin (lon) im Bogenmaß. Dann erstellen Sie eine MySQL-Funktion, wie folgt:
CREATE FUNCTION `geodistance`(`sin_lat1` FLOAT,
`cos_cos1` FLOAT, `cos_sin1` FLOAT,
`sin_lat2` FLOAT,
`cos_cos2` FLOAT, `cos_sin2` FLOAT)
RETURNS float
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY INVOKER
BEGIN
RETURN acos(sin_lat1*sin_lat2 + cos_cos1*cos_cos2 + cos_sin1*cos_sin2);
END
Dies gibt Ihnen die Entfernung.
Vergessen Sie nicht, einen Index für lat / lon hinzuzufügen, damit das Begrenzungsfeld die Suche unterstützen kann, anstatt es zu verlangsamen (der Index wurde bereits in der obigen Abfrage CREATE TABLE hinzugefügt).
INDEX `lat_lon_idx` (`lat`, `lon`)
Bei einer alten Tabelle mit nur Lat / Lon-Koordinaten können Sie ein Skript einrichten, um es folgendermaßen zu aktualisieren: (PHP mit Meekrodb)
$users = DB::query('SELECT id,lat,lon FROM Old_Coordinates');
foreach ($users as $user)
{
$lat_rad = deg2rad($user['lat']);
$lon_rad = deg2rad($user['lon']);
DB::replace('Coordinates', array(
'object_id' => $user['id'],
'object_type' => 0,
'sin_lat' => sin($lat_rad),
'cos_cos' => cos($lat_rad)*cos($lon_rad),
'cos_sin' => cos($lat_rad)*sin($lon_rad),
'lat' => $user['lat'],
'lon' => $user['lon']
));
}
Anschließend optimieren Sie die eigentliche Abfrage so, dass die Entfernungsberechnung nur dann durchgeführt wird, wenn sie wirklich benötigt wird, indem Sie beispielsweise den Kreis (gut, oval) von innen und außen begrenzen. Dazu müssen Sie mehrere Metriken für die Abfrage selbst vorberechnen:
// assuming the search center coordinates are $lat and $lon in degrees
// and radius in km is given in $distance
$lat_rad = deg2rad($lat);
$lon_rad = deg2rad($lon);
$R = 6371; // earth's radius, km
$distance_rad = $distance/$R;
$distance_rad_plus = $distance_rad * 1.06; // ovality error for outer bounding box
$dist_deg_lat = rad2deg($distance_rad_plus); //outer bounding box
$dist_deg_lon = rad2deg($distance_rad_plus/cos(deg2rad($lat)));
$dist_deg_lat_small = rad2deg($distance_rad/sqrt(2)); //inner bounding box
$dist_deg_lon_small = rad2deg($distance_rad/cos(deg2rad($lat))/sqrt(2));
In Anbetracht dieser Vorbereitungen geht die Abfrage ungefähr so (php):
$neighbors = DB::query("SELECT id, type, lat, lon,
geodistance(sin_lat,cos_cos,cos_sin,%d,%d,%d) as distance
FROM Coordinates WHERE
lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d
HAVING (lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d) OR distance <= %d",
// center radian values: sin_lat, cos_cos, cos_sin
sin($lat_rad),cos($lat_rad)*cos($lon_rad),cos($lat_rad)*sin($lon_rad),
// min_lat, max_lat, min_lon, max_lon for the outside box
$lat-$dist_deg_lat,$lat+$dist_deg_lat,
$lon-$dist_deg_lon,$lon+$dist_deg_lon,
// min_lat, max_lat, min_lon, max_lon for the inside box
$lat-$dist_deg_lat_small,$lat+$dist_deg_lat_small,
$lon-$dist_deg_lon_small,$lon+$dist_deg_lon_small,
// distance in radians
$distance_rad);
EXPLAIN in der obigen Abfrage könnte bedeuten, dass kein Index verwendet wird, es sei denn, es gibt genügend Ergebnisse, um einen solchen auszulösen. Der Index wird verwendet, wenn die Koordinatentabelle genügend Daten enthält. Sie können FORCE INDEX (lat_lon_idx) zum SELECT hinzufügen, damit es den Index ohne Rücksicht auf die Tabellengröße verwendet, sodass Sie mit EXPLAIN überprüfen können, ob er ordnungsgemäß funktioniert.
Mit den obigen Codebeispielen sollten Sie eine funktionierende und skalierbare Implementierung der Objektsuche nach Entfernung mit minimalem Fehler haben.