MYSQL - левые объединения, оптимизация 2-секундного запроса - PullRequest
0 голосов
/ 30 августа 2011

Я пытаюсь оптимизировать свой запрос MySQL, я объединил все таблицы в одну таблицу, где это кажется полезным.

Но запрос по-прежнему занимает более 2 секунд ... Есть ли способ сделать его быстрее?

On user_id - это всегда индекс.MySQL 5.5.12, все таблицы, кроме городов, являются таблицами InnoDB.

SELECT b.user_id,b.firstname,b.lastname,b.address,b.zipcode,b.city
  , ($calculatings) AS Distance
FROM `user_profiles` `b`
LEFT JOIN `cities` `a` ON `a`.`postal`=`b`.`zipcode` 
JOIN `users` `u` ON `b`.`user_id`=`u`.`id` 
JOIN `user_consultants` `c` ON `b`.`user_id`=`c`.`user_id` 
WHERE ($calculatings) <= 25 
      AND c.incorporated='1' 
      AND u.typ='1' 
      AND u.activated='1' 
      AND u.banned='0' 
ORDER BY Distance ASC, c.zsum_score DESC 
LIMIT 30

В var $ calculatings приведены математические операции для расчета расстояния (уже оптимизированы).

$ пример расчета:

6368 * SQRT(2*(1-cos(RADIANS(`a`.`lat`)) * cos(0.840105508801) * 
(sin(RADIANS(`a`.`lon`)) * sin(0.201952047748) + cos(RADIANS(`a`.`lon`)) * 
cos(0.201952047748)) - sin(RADIANS(`a`.`lat`)) * sin(0.840105508801)))

Почему так много левых соединений?

  1. user_profiles - это подробная таблица пользовательских данных
  2. города - это таблица городов свсе города моей страны с широтой и долготой и дополнительной информацией
  3. users - основная таблица пользователей для имени пользователя и пароля, хэшей, попыток входа в систему, запретов и т. д.для специальной группы пользователей

Размеры

  1. user_profiles 112.000 строк
  2. городов 68.000 строк
  3. пользователей246 000 строк
  4. user_consultants 98 000 строк

Объяснение SQL enter image description here (полный щелчок правой кнопкой мыши)

Ответы [ 5 ]

2 голосов
/ 30 августа 2011

У меня нет времени, чтобы написать полную информацию, но для оптимизации пространственного поиска, вот быстрый ответ:

Сохраните пару (latitude, longitude) в таблице (MyISAM) как пространственное поле: POINT (aвариант типа GEOMETRY).

Добавьте в это поле пространственный индекс.

Используйте функцию MBRContains() или MBRWithin() в своем запросе, например,используйте пространственный индекс, чтобы сузить поиск внутри квадрата, который содержит круг с радиусом 25 от вашей базовой точки:

WHERE MBRWithin( cities.myPointField
               , Polygon( @lat-25 @long-25
                        , @lat+25 @long-25
                        , @lat+25 @long+25
                        , @lat-25 @long+25
                        ) 
               )
  AND (yourDistanceCalculation) < 25

Вы можете проверить документы MySQL: Пространственные расширения

1 голос
/ 30 августа 2011

Я думаю, что вы идете по этому поводу немного (но может быть неправильно).Вы запускаете запрос на основе профилей пользователей, но все ваши критерии находятся на самом низком уровне пользователей и уровня консультантов пользователей.Я бы сделал STRAIGHT_JOIN (скажите оптимизатору, что нужно делать в том порядке, в котором ВЫ заявляете).Тогда для ваших объединений выполнение LEFT JOIN не обязательно имеет смысл, если только у вас нет пропущенных значений идентификатора ссылки между таблицами, что позволило бы некоторым записям не иметь заданный город или профиль пользователя.Итак, как я уже сказал, я бы поставил вашу таблицу пользователей на первое место, так как это, вероятно, будет иметь самый ограниченный набор результатов по критериям.Кроме того, есть индекс на (тип, активирован, забанен).Затем ваша таблица user_consultants и индекс для нее (user_id, incorporated).Города должны иметь индекс для почтовых и user_profiles, индекс для почтовых индексов.

Вот последний запрос, который я бы попробовал

select STRAIGHT_JOIN
      b.user_id,
      b.firstname,
      b.lastname,
      b.address,
      b.zipcode,
      b.city, 
      ($calculatings) AS Distance
   from 
      (select u.id, c.zsum_score
          from 
             users u
                join user_consultants c
                   on u.id = c.user_id
                  and c.incorporated = '1'
          where
                 u.typ = '1'
             and u.activated = '1'
             and u.banned = '0' ) PreQuery
      join user_profiles b
         on PreQuery.ID = b.user_id
         join cities a on b.zipcode = a.postal
   where
      ($calculatings) <= 25 
   ORDER BY
      Distance ASC, 
      PreQuery.zsum_score DESC 
   LIMIT 30

Поскольку соединение между пользователем и консультантами пользователей было по идентификатору пользователя, тогда консультанты пользователей и профили пользователей были по идентификатору пользователя, объединение идентификатора «PreQuery» - это то же самое, поэтому нет необходимости повторно объединяться с ОБА таблицами.

1 голос
/ 30 августа 2011

У вас должны быть (как минимум) индексы для следующих городов:

1 голос
/ 30 августа 2011
SELECT 
    b.user_id,b.firstname,b.lastname,b.address,b.zipcode,b.city,
    ($calculatings) AS Distance
FROM 
    `user_profiles` `b`
    JOIN `users` `u` ON `b`.`user_id`=`u`.`id`
     AND `u`.`typ`=1
     AND `u`.`activated`=1
     AND `u`.`banned`=0
    LEFT JOIN `cities` `a` ON `b`.`zipcode`=`a`.`postal` 
    LEFT JOIN `user_consultants` `c` ON `b`.`user_id`=`c`.`user_id` 
WHERE Distance <= 25
ORDER BY Distance ASC, c.zsum_score DESC
LIMIT 0,30

... хотя, если значение $calculatings всегда одно и то же, и, как я вижу, оно зависит только от данных в таблице cities - вы должны просто поместить в него другой столбец, содержащийпредварительно рассчитанное значение расстояния.

Несколько замечаний об изменениях, которые я сделал:

  • Я просто предполагаю, что typ, activated и banned относятся к типуint (угадывание по значениям в вашем запросе) - вы не должны заключать их в кавычки.
  • Я также предполагаю, что, поскольку users является вашей основной таблицей пользователей, каждый user_id из user_profilesдолжен иметь id в users, поэтому вам не нужно, чтобы LEFT.
  • JOIN с выполнялись быстрее, чем WHERE предложения (а WHERE быстрее, чем HAVING, как я вижу другой ответ, который использует его).
  • Как ответил Тудор Константин - вам следует позаботиться об индексации всех столбцов, которые вы используете в качестве ссылок для объединения.
0 голосов
/ 30 августа 2011
SELECT 
    b.user_id,b.firstname,b.lastname,b.address,b.zipcode,b.city, 
    ($calculatings) AS Distance
FROM 
    `user_profiles` `b`
    JOIN `cities` `a` ON `a`.`postal`=`b`.`zipcode` 
    JOIN `users` `u` ON `b`.`user_id`=`u`.`id` 
    JOIN `user_consultants` `c` ON `b`.`user_id`=`c`.`user_id` 
WHERE     
    c.incorporated='1'  AND 
    u.typ='1' AND 
    u.activated='1' AND 
    u.banned='0' 
HAVING
    Distance <= 25
ORDER BY 
    Distance ASC, c.zsum_score DESC 
LIMIT 30
...