Ответ 1
Любая операция, которую вы выполняете здесь, которая работает со всеми строками, будет медленной с тем количеством записей.
Что вам нужно сделать, это воспользоваться преимуществами индекса. Чтобы использовать индекс, это должен быть простой запрос, а НЕ результат функции (как и в настоящее время).
То, что вы делаете, выполняя поиск по радиусу, составляет круг вокруг точки. Используя некоторый триггер до создания круга, мы можем придумать следующий
где S1 - наибольший квадрат внутри, а S2 - наименьший квадрат снаружи.
Теперь мы можем разобрать размеры этих двух квадратов, и все, что НАХОДИТСЯ в S2, попадает и индексируется, и все, что INSIDE S1 попадает в индекс, оставляя только небольшую область, внутри которой теперь нужно искать, используя медленный метод.
Если вам нужно расстояние от точки игнорировать секции S1 (так как все внутри круга нуждается в функции haversine) в качестве примечания здесь, в то время как все внутри круга нуждается в нем, не каждая точка находится на расстоянии, поэтому оба предложения WHERE
по-прежнему необходимы
Итак, давайте вычислим эти точки, используя единичный круг
function getS1S2($latitude, $longitude, $kilometer)
{
$radiusOfEarthKM = 6371;
$latitudeRadians = deg2rad($latitude);
$longitudeRadians = deg2rad($longitude);
$distance = $kilometer / $radiusOfEarthKM;
$deltaLongitude = asin(sin($distance) / cos($latitudeRadians));
$bounds = new \stdClass();
// these are the outer bounds of the circle (S2)
$bounds->minLat = rad2deg($latitudeRadians - $distance);
$bounds->maxLat = rad2deg($latitudeRadians + $distance);
$bounds->minLong = rad2deg($longitudeRadians - $deltaLongitude);
$bounds->maxLong = rad2deg($longitudeRadians + $deltaLongitude);
// and these are the inner bounds (S1)
$bounds->innerMinLat = rad2deg($latitudeRadians + $distance * cos(5 * M_PI_4));
$bounds->innerMaxLat = rad2deg($latitudeRadians + $distance * sin(M_PI_4));
$bounds->innerMinLong = rad2deg($longitudeRadians + $deltaLongitude * sin(5 * M_PI_4));
$bounds->innerMaxLong = rad2deg($longitudeRadians + $deltaLongitude * cos(M_PI_4));
return $bounds;
}
Теперь ваш запрос будет
SELECT
*
FROM
`places`
HAVING p.nlatitude BETWEEN {$bounds->minLat}
AND {$bounds->maxLat}
AND p.nlongitude BETWEEN {$bounds->minLong}
AND {$bounds->maxLong}
AND (
(
p.nlatitude BETWEEN {$bounds->innerMinLat}
AND {$bounds->innerMaxLat}
AND p.nlongitude BETWEEN {$bounds->innerMinLong}
AND {$bounds->innerMaxLong}
)
OR (
6371 * ACOS(
COS(RADIANS({ $lat })) * COS(RADIANS(`latitude`)) * COS(
RADIANS(`longitude`) - RADIANS({ $lon })
) + SIN(RADIANS({ $lat })) * SIN(RADIANS(`latitude`))
)
)
)) <= {$radius}
ORDER BY distance ASC
ВАЖНО
Вышеприведенный текст имеет читаемость, убедитесь, что эти значения экранированы правильно/желательно параметризованы
Затем это может воспользоваться индексом и разрешить соединение быстрее в течение
Добавление объединения становится
SELECT
*
FROM
`places` p
INNER JOIN my_friends f ON f.id = p.id
WHERE p.latitude BETWEEN {$bounds->minLat}
AND {$bounds->maxLat}
AND p.longitude BETWEEN {$bounds->minLong}
AND {$bounds->maxLong}
AND (
(
p.latitude BETWEEN {$bounds->innerMinLat}
AND {$bounds->innerMaxLat}
AND p.longitude BETWEEN {$bounds->innerMinLong}
AND {$bounds->innerMaxLong}
)
OR (
6371 * ACOS(
COS(RADIANS({ $lat })) * COS(RADIANS(`latitude`)) * COS(
RADIANS(`longitude`) - RADIANS({ $lon })
) + SIN(RADIANS({ $lat })) * SIN(RADIANS(`latitude`))
)
)
) <= {$radius}
AND f.personal_id = {$personal_id}
ORDER BY distance ASC
ВАЖНО
Вышеприведенный текст имеет читаемость, убедитесь, что эти значения экранированы правильно/желательно параметризованы
Предполагая, что у вас есть правильные индексы, этот запрос должен оставаться быстрым и позволить вам выполнить соединение.
Глядя на код выше, я не уверен, откуда personal_id
приходит, так что он остался как есть
если вам нужно расстояние от запроса, вы можете удалить квадрат S1
(
p.latitude BETWEEN {$bounds->innerMinLat}
AND {$bounds->innerMaxLat}
AND p.longitude BETWEEN {$bounds->innerMinLong}
AND {$bounds->innerMaxLong}
)
и переместите вторую часть этого OR
6371 * ACOS(
COS(RADIANS({ $lat })) * COS(RADIANS(`latitude`)) * COS(
RADIANS(`longitude`) - RADIANS({ $lon })
) + SIN(RADIANS({ $lat })) * SIN(RADIANS(`latitude`))
)
назад к выбору, который все еще использует S2.
Я также должен был бы удалить "магическое число" в запросе 6371 - это радиус земли в километре