Ускорить запрос для большой таблицы - PullRequest
0 голосов
/ 08 апреля 2019

Я создаю приложение с геолокацией, поэтому у меня есть таблица городов с более чем 2 миллионами записей, и я хочу получить ближайшую запись с определенной широтой и долготой, имеющую индекс по обоим столбцам. Код понимает, чего я хочу, но для его достижения требуется от 3 до 4 секунд.

Я пытался указать индекс по широте и долготе и только по долготе. Я также пытался отдельно указать индекс широты и долготы.

SELECT * FROM Cities 
ORDER BY ABS(someLatitude - latitude) ASC, ABS(someLongitude - longitude) ASC 
LIMIT 1

Я ожидаю, что код будет выполнен менее чем за секунду. Что я могу сделать?

1 Ответ

0 голосов
/ 22 апреля 2019
ORDER BY ABS(someLatitude - latitude) ASC,
         ABS(someLongitude - longitude) ASC 

WTF? Это соберет вместе все города с очень похожими широтами - даже те, что находятся на другом конце земного шара!

mysql> SELECT country, city, lat, lng FROM `cities`
       WHERE population > 0
       ORDER BY ABS(lat - 36) ASC,
                ABS(lng - 0) ASC LIMIT 10;
+---------+-----------+---------+----------+
| country | city      | lat     | lng      |
+---------+-----------+---------+----------+
| jp      | Okegawa   |      36 |  139.557 |
| us      | Sapulpa   | 35.9986 | -96.1139 |
| us      | Avenal    | 36.0042 | -120.128 |
| cn      | Zhucheng  | 35.9947 |  119.397 |
| us      | Durham    | 35.9939 | -78.8989 |
| us      | Espanola  | 35.9911 |  -106.08 |
| jp      | Chichibu  | 35.9903 |  139.076 |
| us      | Oak Ridge | 36.0103 | -84.2697 |
| ir      | Baneh     | 35.9894 |  45.8953 |
| es      | Tarifa    | 36.0125 | -5.60556 |
+---------+-----------+---------+----------+
10 rows in set (0.90 sec)

Обратите внимание, что города в Японии, США, Китае, Ирландии и Испании "близки" друг к другу на основании этого ORDER BY. (В моем списке 3M городов, многие с населением = 0.)

Первая оптимизация - выбрать максимальное расстояние и нарисовать «ограничивающую рамку» вокруг центра. И имеют индекс:

    mysql> SELECT country, city, lat, lng FROM `cities` WHERE population > 0
            -> AND lat BETWEEN 36-2 AND 36+2
            -> AND lng BETWEEN -84-3 AND -84+3
            -> LIMIT 10;                                                                                    
        +---------+---------------+---------+----------+
        | country | city          | lat     | lng      |
        +---------+---------------+---------+----------+
        | us      | Gadsden       | 34.0142 | -86.0067 |
        | us      | Cedartown     | 34.0536 |  -85.255 |
        | us      | Acworth       | 34.0658 | -84.6769 |
        | us      | Kennesaw      | 34.0233 | -84.6156 |
        | us      | Woodstock     | 34.1014 | -84.5194 |
        | us      | Mountain Park | 34.0808 | -84.4114 |
        | us      | Roswell       | 34.0231 | -84.3617 |
        | us      | Alpharetta    | 34.0753 | -84.2942 |
        | us      | Duluth        | 34.0028 | -84.1447 |
        | us      | Suwanee       | 34.0514 | -84.0714 |
        +---------+---------------+---------+----------+

с INDEX(lat) или INDEX(lng) или INDEX(lat,lng) или INDEX(lng,lat). (Эти индексы примерно одинаково хороши / плохи. Но все же нужно было посмотреть на 257 тыс. Строк, а именно на все строки в этой 4-градусной (276-мильной или 444-километровой) полосе.

Если вы не ограничите расстояние, индекс будет бесполезен, поскольку он будет смотреть на все строки.

Для вычисления расстояния (либо пифагорейского, либо большого круга) для получения «ближайшего» потребуется какое-то условие ORDER BY.

Чтобы стать действительно эффективным, нужно гораздо больше усилий: http://mysql.rjweb.org/doc.php/latlng В нем подробно обсуждается, почему проблема является сложной.

...