2012-02-27 4 views
14

La mia situazione:Come ottimizzare query lente con molte giunture

  • la query ricerche circa 90.000 veicoli
  • la query richiede molto ogni volta
  • Ho già indici su tutti i campi da unire.

Come ottimizzarlo?

Ecco la domanda:

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) 

Ecco lo schema della tabella del veicolo:

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 ; 

Il spiego:

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 
+0

Sei in grado di fornire una SPIEGA? –

+0

Post originale aggiornato. – ChimeraTheory

risposta

11

Migliorare la clausola WHERE

tuo SPIEGARE spettacoli che MySQL sta utilizzando un solo indice (type_id) per selezionare le righe che corrispondono alla clausola WHERE, anche se si dispone di più criteri nei clausola.

Per poter utilizzare un indice per tutti i criteri nella clausola WHERE e per ridurre la dimensione del set di risultati il ​​più rapidamente possibile, aggiungere un indice a più colonne sulle seguenti colonne nella tabella dei veicoli:

(status, date_from, date_to, type_id, price) 

Le colonne devono essere almeno in ordine di cardinalità massima.

Ad esempio, vehicles.date_from rischia di avere valori più distinti di status, quindi mettere la colonna date_from prima status, in questo modo:

(date_from, date_to, price, type_id, status) 

Ciò dovrebbe ridurre le righe restituite nella prima parte della query e dovrebbe essere dimostrato con un numero di righe inferiore sulla prima riga del risultato EXPLAIN.

Si noterà inoltre che MySQL utilizzerà l'indice multi-colonna per WHERE nel risultato EXPLAIN. Se, per caso, non lo fa, dovresti suggerire o forzare l'indice multi-colonna.

Rimozione del superfluo JOINs

Non sembra che si sta utilizzando tutti i campi in una qualsiasi delle tabelle unite, in modo da rimuovere le giunzioni. Ciò rimuoverà tutto il lavoro aggiuntivo della query e ti porterà a un piano di esecuzione semplice (una riga nel risultato EXPLAIN).

Ogni tabella JOINed causa una ricerca aggiuntiva per riga del set di risultati. Quindi, se la clausola WHERE seleziona 5.000 file dai veicoli, poiché hai 8 join per i veicoli, avrai 5.000 * 8 = 40.000 ricerche. Questo è molto da chiedere al tuo server di database.

+0

Se i campi che stiamo comparando provengono da tabelle unite, l'indicizzazione sarà utile? –

1

si fa anche avere inde xes su questi:

vehicles.status 
vehicles.date_from 
vehicles.date_to 
vehicles.type_id 
vehicles.price 
+0

Sì, vedi sopra. – ChimeraTheory

1

di essere un po 'più specifico di @Randy di indici, credo che la sua intenzione era quella di avere un indice composito di approfittare dei vostri critieria interrogazione ... un indice che si basa su un MINIMO di ...

(status, type_id, date_from) 

ma potrebbe essere esteso per includere il date_to e prezzo troppo, ma non so quanto l'indice a quel livello granulare potrebbe effettivamente aiutare

(status, type_id, date_from, date_to, price) 

MODIFICA per commenti

Non è necessario disporre di tutti quei singoli indici ... Sì, la chiave primaria da sola. Tuttavia, per gli altri, dovresti avere indici composti basati su quali potrebbero essere i tuoi criteri di interrogazione comuni e rimuovere gli altri ... il motore potrebbe confondersi su quale potrebbe essere più adatto alla query. Se sai di essere sempre alla ricerca di un determinato stato, tipo e data (supponendo le ricerche dei veicoli), fallo come un indice. Se la query è alla ricerca di tali informazioni, ma anche i prezzi all'interno di tali criteri sarà già molto vicino ai pochi record indicizzati che si qualificano e superano il prezzo come un criterio in più.

Se offrite query come Solo trasmissione automatica o manuale indipendentemente dall'anno/dalla marca, allora sì, potrebbe essere un indice a parte. Tuttavia, se in genere avessi qualche altro criterio "comune", aggiungilo come secondario che può essere utilizzato nella query. Es .: se cerchi le trasmissioni manuali che sono 2 porte o 4 porte, hai il tuo indice su (transmission_id, category_id).

Ancora una volta, si vuole tutto ciò che può aiutare a restringere il campo dei criteri basati su alcune condizioni "minime". Se affili su una colonna aggiuntiva all'indice che potrebbe essere "comunemente" applicata, ciò dovrebbe solo aiutare la performance.

+0

Non ho familiarità con gli indici composti - si prega di consultare il mio post aggiornato. La mia indicizzazione attuale è inefficiente? – ChimeraTheory

+0

Basta aggiungere un altro indice, ma invece di una colonna SINGLE, basta fare come sopra, più colonne separate da colonne ... In questo modo, un indice può avere più componenti per corrispondere più strettamente ai criteri di query. – DRapp

+0

Ho 21 indici, che devo raggruppare e devo quindi rimuoverli prima? – ChimeraTheory

4

Invece del costoso calcolo della distanza precisa per tutte le delle righe utilizzano un riquadro di delimitazione e calcolano la distanza esatta solo per le righe all'interno della casella.

L'esempio più semplice possibile è calcolare la longitudine e la latitudine min/max che ti interessano e aggiungerlo alla clausola WHERE. In questo modo la distanza verrà calcolata solo per un sottoinsieme di righe.

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

Per soluzioni più complesse si veda:

3

è che si SQL più veloce senza questo?

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 

esecuzione di equazione matematica è molto costoso

Forse si dovrebbe prendere in considerazione una vista materializzata che pre-calcolare voi a distanza, ed è possibile selezionare da questo punto di vista. A seconda della dinamica dei tuoi dati, potresti non dover aggiornare i dati troppo spesso.

+0

Un po 'sì, ma ho bisogno di quello lì ... – ChimeraTheory