Как оптимизировать медленный запрос со многими объединениями

Моя ситуация:

  • запрос ищет около 90 000 автомобилей.
  • запрос занимает много времени каждый раз
  • У меня уже есть индексы по всем полям, в которые вы подключались.

Как его оптимизировать?

Вот запрос:

SELECT vehicles.make_id,
       vehicles.fuel_id,
       vehicles.body_id,
       vehicles.transmission_id,
       vehicles.colour_id,
       vehicles.mileage,
       vehicles.vehicle_year,
       vehicles.engine_size,
       vehicles.trade_or_private,
       vehicles.doors,
       vehicles.model_id,
       Round(3959 * Acos(Cos(Radians(51.465436)) *
                         Cos(Radians(vehicles.gps_lat)) *
                                           Cos(
                                           Radians(vehicles.gps_lon) - Radians(
                                           -0.296482)) +
                               Sin(
                                      Radians(51.465436)) * Sin(
                               Radians(vehicles.gps_lat)))) AS distance
FROM   vehicles
       INNER JOIN vehicles_makes
         ON vehicles.make_id = vehicles_makes.id
       LEFT JOIN vehicles_models
         ON vehicles.model_id = vehicles_models.id
       LEFT JOIN vehicles_fuel
         ON vehicles.fuel_id = vehicles_fuel.id
       LEFT JOIN vehicles_transmissions
         ON vehicles.transmission_id = vehicles_transmissions.id
       LEFT JOIN vehicles_axles
         ON vehicles.axle_id = vehicles_axles.id
       LEFT JOIN vehicles_sub_years
         ON vehicles.sub_year_id = vehicles_sub_years.id
       INNER JOIN members
         ON vehicles.member_id = members.id
       LEFT JOIN vehicles_categories
         ON vehicles.category_id = vehicles_categories.id
WHERE  vehicles.status = 1
       AND vehicles.date_from < 1330349235
       AND vehicles.date_to > 1330349235
       AND vehicles.type_id = 1
       AND ( vehicles.price >= 0
             AND vehicles.price <= 1000000 )  

Вот схема таблиц транспортных средств:

CREATE TABLE IF NOT EXISTS `vehicles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `number_plate` varchar(100) NOT NULL,
  `type_id` int(11) NOT NULL,
  `make_id` int(11) NOT NULL,
  `model_id` int(11) NOT NULL,
  `model_sub_type` varchar(250) NOT NULL,
  `engine_size` decimal(12,1) NOT NULL,
  `vehicle_year` int(11) NOT NULL,
  `sub_year_id` int(11) NOT NULL,
  `mileage` int(11) NOT NULL,
  `fuel_id` int(11) NOT NULL,
  `transmission_id` int(11) NOT NULL,
  `price` decimal(12,2) NOT NULL,
  `trade_or_private` tinyint(4) NOT NULL,
  `postcode` varchar(25) NOT NULL,
  `gps_lat` varchar(50) NOT NULL,
  `gps_lon` varchar(50) NOT NULL,
  `img1` varchar(100) NOT NULL,
  `img2` varchar(100) NOT NULL,
  `img3` varchar(100) NOT NULL,
  `img4` varchar(100) NOT NULL,
  `img5` varchar(100) NOT NULL,
  `img6` varchar(100) NOT NULL,
  `img7` varchar(100) NOT NULL,
  `img8` varchar(100) NOT NULL,
  `img9` varchar(100) NOT NULL,
  `img10` varchar(100) NOT NULL,
  `is_featured` tinyint(4) NOT NULL,
  `body_id` int(11) NOT NULL,
  `colour_id` int(11) NOT NULL,
  `doors` tinyint(4) NOT NULL,
  `axle_id` int(11) NOT NULL,
  `category_id` int(11) NOT NULL,
  `contents` text NOT NULL,
  `date_created` int(11) NOT NULL,
  `date_edited` int(11) NOT NULL,
  `date_from` int(11) NOT NULL,
  `date_to` int(11) NOT NULL,
  `member_id` int(11) NOT NULL,
  `inactive_id` int(11) NOT NULL,
  `status` tinyint(4) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `type_id` (`type_id`),
  KEY `make_id` (`make_id`),
  KEY `model_id` (`model_id`),
  KEY `fuel_id` (`fuel_id`),
  KEY `transmission_id` (`transmission_id`),
  KEY `body_id` (`body_id`),
  KEY `colour_id` (`colour_id`),
  KEY `axle_id` (`axle_id`),
  KEY `category_id` (`category_id`),
  KEY `vehicle_year` (`vehicle_year`),
  KEY `mileage` (`mileage`),
  KEY `status` (`status`),
  KEY `date_from` (`date_from`),
  KEY `date_to` (`date_to`),
  KEY `trade_or_private` (`trade_or_private`),
  KEY `doors` (`doors`),
  KEY `price` (`price`),
  KEY `engine_size` (`engine_size`),
  KEY `sub_year_id` (`sub_year_id`),
  KEY `member_id` (`member_id`),
  KEY `date_created` (`date_created`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=136237 ;

EXPLAIN:

1   SIMPLE  vehicles    ref     type_id,make_id,status,date_from,date_to,price,mem...   type_id     4   const   85695   Using where
1   SIMPLE  members     index   PRIMARY     PRIMARY     4   NULL    3   Using where; Using index; Using join buffer
1   SIMPLE  vehicles_makes  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.make_id    1   Using index
1   SIMPLE  vehicles_models     eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.model_id   1   Using index
1   SIMPLE  vehicles_fuel   eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.fuel_id    1   Using index
1   SIMPLE  vehicles_transmissions  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.transmission_id    1   Using index
1   SIMPLE  vehicles_axles  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.axle_id    1   Using index
1   SIMPLE  vehicles_sub_years  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.sub_year_id    1   Using index
1   SIMPLE  vehicles_categories     eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.category_id    1   Using index

Ответы

Ответ 1

Улучшение предложения WHERE

Ваш EXPLAIN показывает, что MySQL использует только один индекс (type_id) для выбора строк, которые соответствуют предложению WHERE, даже если в предложении есть несколько критериев.

Чтобы иметь возможность использовать индекс для всех критериев в предложении WHERE и как можно быстрее уменьшить размер набора результатов, добавьте индекс нескольких столбцов в следующие столбцы таблицы транспортных средств:

(status, date_from, date_to, type_id, price)

Столбцы должны быть в порядке наивысшей мощности по меньшей мере.

Например, vehicles.date_from, скорее всего, имеет более четкие значения, чем status, поэтому поставьте столбец date_from до status, например:

(date_from, date_to, price, type_id, status)

Это должно уменьшить строки, возвращаемые в первой части выполнения запроса, и должно быть продемонстрировано с более низким количеством строк в первой строке результата EXPLAIN.

Вы также заметите, что MySQL будет использовать индекс с несколькими столбцами для WHERE в результате EXPLAIN. Если, случайно, это не так, вы должны намекать или заставлять индекс с несколькими столбцами.

Удаление ненужных JOINs

Не похоже, что вы используете какие-либо поля в любой из объединенных таблиц, поэтому удалите соединения. Это приведет к удалению всей дополнительной работы запроса и приведет вас к одному простому плану выполнения (одна строка в результате EXPLAIN).

Каждая таблица JOINed вызывает дополнительный поиск в строке набора результатов. Итак, если предложение WHERE выбирает 5000 строк из транспортных средств, так как у вас есть 8 соединений на транспортных средствах, у вас будет 5 000 * 8 = 40 000 запросов. Это много, чтобы спросить у вашего сервера базы данных.

Ответ 2

Вместо дорогостоящего вычисления точного расстояния для всех строк используйте ограничивающий прямоугольник и вычисляйте точное расстояние только для строк внутри поля.

Самый простой возможный пример - рассчитать минимальную/максимальную долготу и широту, которые вас интересуют, и добавить ее в предложение WHERE. Таким образом, расстояние будет рассчитываться только для подмножества строк.

WHERE
    vehicles.gps_lat > min_lat ANDd vehicles.gps_lat < max_lat AND
    vehicles.gps_lon > min_lon AND vehicles.gps_lon < max_lon

Для более сложных решений см.

Ответ 3

Является ли ваш SQL быстрее без этого?

Round(3959 * Acos(Cos(Radians(51.465436)) *
  Cos(Radians(vehicles.gps_lat)) *
  Cos(Radians(vehicles.gps_lon) - 
  Radians(-0.296482)) + 
  Sin(Radians(51.465436)) * 
  Sin(Radians(vehicles.gps_lat)))) AS distance

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

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

Ответ 4

У вас также есть индексы на них:

vehicles.status
vehicles.date_from
vehicles.date_to
vehicles.type_id
vehicles.price

Ответ 5

Чтобы быть немного более конкретным, чем @Randy индексов, я считаю, что его намерение состояло в том, чтобы иметь индекс COMPOUND, чтобы использовать ваши критерии запроса... Один индекс, построенный на MINIMUM...

( status, type_id, date_from )

но может быть расширен, чтобы включить date_to и цену, но не знаю, насколько индекс на этом гранулированном уровне может действительно помочь

( status, type_id, date_from, date_to, price )

EDIT за комментарии

Вам не нужны все эти индивидуальные индексы... Да, первичный ключ сам по себе. Тем не менее, для других, у вас должны быть составные индексы, основанные на ваших общих критериях запроса и удалении других... двигатель может запутаться, что может быть лучше всего подходит для запроса. Если вы знаете, что всегда ищете определенный статус, тип и дату (при поиске автомобилей), сделайте это как один индекс. Если запрос ищет такую ​​информацию, но и цены в рамках этих критериев, он уже будет очень близок к нескольким индексированным записям, которые квалифицируются и пролетают по цене как дополнительные критерии.

Если вы предлагаете такие запросы, как "Только автоматическая" или "Ручная", независимо от года/периода, тогда да, это может быть собственный индекс. Однако, если вы TYPICALLY имеете некоторые другие "общие" критерии, примените это как вторичное, которое МОЖЕТ быть использовано в запросе. Пример: если вы ищете Ручные трансмиссии, которые являются 2-дверными и 4-дверными, укажите свой индекс (transmission_id, category_id).

Опять же, вы хотите все, что поможет сузить поле критериев, основанное на некотором "минимальном" условии. Если вы ссылаетесь на дополнительный столбец на индекс, который может быть "обычно", это должно только помочь производительности.