좌표 테이블에 도우미 필드를 추가하면 쿼리의 응답 시간을 향상시킬 수 있습니다.
이처럼 :
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`)
)
TokuDB를 사용하는 경우 다음과 같이 술어 중 하나에 클러스터링 인덱스를 추가하면 성능이 훨씬 향상됩니다.
alter table Coordinates add clustering index c_lat(lat);
alter table Coordinates add clustering index c_lon(lon);
각 포인트마다 기본 위도 및 경도뿐만 아니라 라디안의 sin (lat), 라디안의 cos (lat) * cos (lon) 및 라디안의 cos (lat) * sin (lon)이 필요합니다. 그런 다음 다음과 같이 mysql 함수를 작성하십시오.
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
이것은 당신에게 거리를 제공합니다.
위도 / 경도에 인덱스를 추가해야 경계 상자가 검색 속도를 늦추지 않고 검색에 도움을 줄 수 있습니다 (인덱스가 위의 CREATE TABLE 쿼리에 이미 추가되어 있음).
INDEX `lat_lon_idx` (`lat`, `lon`)
위도 / 경도 좌표 만있는 오래된 테이블이있는 경우 다음과 같이 업데이트하도록 스크립트를 설정할 수 있습니다 (php를 사용하는 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']
));
}
그런 다음 실제로 필요한 경우에만 거리 계산을 수행하도록 실제 쿼리를 최적화합니다 (예 : 내외부에서 원 (타원, 타원) 경계). 이를 위해 쿼리 자체에 대한 몇 가지 메트릭을 미리 계산해야합니다.
// 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));
이러한 준비가 주어지면 쿼리는 다음과 같습니다 (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은 트리거 할 결과가 충분하지 않으면 인덱스를 사용하지 않는다고 말할 수 있습니다. 좌표 테이블에 충분한 데이터가있는 경우 인덱스가 사용됩니다. 테이블 크기와 상관없이 인덱스를 사용하도록 SELECT에 FORCE INDEX (lat_lon_idx)를 추가 할 수 있으므로 EXPLAIN을 사용하여 올바르게 작동하는지 확인할 수 있습니다.
위의 코드 샘플을 사용하면 최소한의 오류로 거리별로 객체 검색을 작동하고 확장 가능하게 구현해야합니다.