web-dev-qa-db-fra.com

Comment optimiser une requête lente avec de nombreuses jointures

Ma situation:

  • la requête recherche environ 90 000 véhicules
  • la requête prend longtemps chaque fois
  • J'ai déjà des index sur tous les champs joints.

Comment puis-je l'optimiser?

Voici la requête:

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 )  

Voici le schéma de la table de véhicule :

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 ;

l'expliquer :

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
21
ChimeraTheory

Améliorer la clause d'où

Votre explique montre que MySQL n'utilise qu'un index (type_id) Pour sélectionner les lignes correspondant à la clause WHERE, même si vous avez plusieurs critères de la clause.

Pour pouvoir utiliser un index pour tous les critères de la clause où la clause WHERE et réduire la taille du résultat défini le plus rapidement possible, ajoutez un index multi-colonnes sur les colonnes suivantes sur la table des véhicules:

(status, date_from, date_to, type_id, price)

Les colonnes doivent être en ordre de la plus haute cardinalité au moins.

Par exemple, vehicles.date_from est susceptible d'avoir plus de valeurs distinctes que status, alors mettez le date_from colonne avant status, comme ceci:

(date_from, date_to, price, type_id, status)

Cela devrait réduire les lignes retournées dans la première partie de l'exécution de la requête et doit être démontrée avec un nombre de rangées inférieur sur la première ligne du résultat expliqué.

Vous remarquerez également que MySQL utilisera l'indice multi-colonnes pour le résultat expliqué. Si, par hasard, cela ne signifie pas, vous devez indiquer ou forcer l'index multi-colonnes.

Suppression des jointures inutiles

Il n'apparaît pas que vous utilisez des champs dans l'une des tables jointes, alors retirez les jointures. Cela supprimera tous les travaux supplémentaires de la requête et vous remettra à un seul plan d'exécution simple (une ligne dans le résultat expliqué).

Chaque table jointe provoque une recherche supplémentaire par ligne du jeu de résultats. Donc, si la clause d'où sélectionne 5 000 rangées des véhicules, car vous disposez de 8 jointures aux véhicules, vous aurez 5 000 * 8 = 40 000 recherches. C'est beaucoup à poser à votre serveur de base de données.

14
Marcus Adams

Au lieu d'un calcul coûteux de distance précise pour Tout des lignes utilisez une boîte de sélection et calculez la distance exacte uniquement pour les lignes à l'intérieur de la boîte.

L'exemple le plus simple possible consiste à calculer la longitude min/maximum et la latitude qui vous intéresse et l'ajoutez à WHERE clause. De cette façon, la distance ne sera calculée que pour un sous-ensemble de lignes.

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

Pour plus de solutions complexes, voir:

4
Mariusz Jamro

Pour être un peu plus spécifique que @Randy of Indexes, je pense que son intention était d'avoir un index composé pour profiter de votre critique de critique ... Un indice construit sur un minimum de ...

( status, type_id, date_from )

mais pourrait être étendu à inclure la date_to et le prix aussi, mais je ne sais pas combien l'indice à ce niveau granulaire pourrait réellement aider

( status, type_id, date_from, date_to, price )

édition par commentaires

Vous ne devriez pas avoir besoin de tous ces index individuels ... Oui, la clé primaire en soi. Toutefois, pour les autres, vous devez avoir des index composés en fonction de vos critères de requête courants et de retirer les autres ... Le moteur pourrait être confondu sur lequel pourrait être mieux adapté à la requête. Si vous savez que vous recherchez toujours un certain statut, tapez et date (en supposant des recherches de véhicule), faites-le comme un indice. Si la requête recherche de telles informations, mais aussi des prix dans ces critères, il sera déjà très proche des rares enregistrements indexés qui se qualifient et volent à travers le prix, à titre de critère supplémentaire.

Si vous proposez une interrogation comme une seule transmission automatique du manuel VS indépendamment de l'année/de la marque, alors oui, cela pourrait être un indice de son propre. Toutefois, si vous auriez généralement des critères "communs", cliquez sur le suivant en tant que secondaire pouvant être utilisé dans la requête. Ex: Si vous recherchez des transmissions manuelles de 2 portes VS 4-Portes, avez votre index sur (transmission_id, catégorie_id).

Encore une fois, vous voulez que tout ce qui aidera à réduire le domaine des critères basés sur une condition "minimale". Si vous cliquez sur une colonne supplémentaire à l'indice qui pourrait être appliqué "communément", cela ne devrait qu'aider la performance.

1
DRapp

Pour clarifier cela comme une réponse: Si vous n'avez pas déjà ces index, vous devriez envisager d'ajouter

avez-vous également des index sur ceux-ci:

vehicles.status
vehicles.date_from
vehicles.date_to
vehicles.type_id
vehicles.price
1
Randy