2012-02-27 17 views
14

Mi situación:Cómo optimizar consultas lentas con muchos une

  • la consulta busca en alrededor de 90.000 vehículos
  • la consulta tarda mucho tiempo cada vez que
  • Ya tengo índices en todos los campos que se van a unir.

¿Cómo puedo optimizarlo?

Ésta es la consulta:

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) 

Aquí es el esquema de la tabla vehículo:

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 ; 

El explico:

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

¿Eres capaz de proporcionar un EXPLAIN? –

+0

Publicación original actualizada. – ChimeraTheory

Respuesta

11

La mejora de la cláusula WHERE

Su EXPLICAR muestra que MySQL sólo es la utilización de un índice (type_id) para seleccionar las filas que coinciden con la cláusula WHERE, a pesar de que tiene varios criterios en la cláusula.

Para poder utilizar un índice para todos los criterios en la cláusula WHERE, y para reducir el tamaño del conjunto de resultados lo más rápido posible, agregue un índice de varias columnas en las siguientes columnas en la tabla de vehículos:

(status, date_from, date_to, type_id, price) 

Las columnas deben estar en orden de mayor cardinalidad al mínimo.

Por ejemplo, vehicles.date_from es probable que tenga más valores distintos de status, a fin de poner la columna de la date_from antes status, así:

(date_from, date_to, price, type_id, status) 

Esto debería reducir las filas devueltas en la primera parte de la ejecución de la consulta , y se debe demostrar con un recuento de filas más bajo en la primera línea del resultado de EXPLAIN.

También notará que MySQL usará el índice de varias columnas para DONDE en el resultado de EXPLAIN. Si, por casualidad, no es así, debe indicar u obligar al índice de varias columnas.

Extracción de la innecesaria JOINs

No parece que está utilizando cualquiera de los campos en cualquiera de las tablas combinadas, por lo que eliminar los puntos de unión. Esto eliminará todo el trabajo adicional de la consulta y lo llevará a un solo plan de ejecución simple (una línea en el resultado de EXPLAIN).

Cada tabla JOINed provoca una búsqueda adicional por fila del conjunto de resultados. Por lo tanto, si la cláusula WHERE selecciona 5.000 filas de vehículos, ya que tiene 8 uniones a vehículos, tendrá 5.000 * 8 = 40.000 búsquedas. Eso es mucho pedir desde su servidor de base de datos.

+0

Si los campos que estamos comparando provienen de tablas unidas, ¿será útil la indexación? –

1

hacer también tienen inde equis sobre éstos:

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

Sí, ver arriba. – ChimeraTheory

1

a ser un poco más específico que @Randy de índices, creo que su intención era tener un índice compuesto que tomar ventaja de sus critieria Consulta de ... Un índice que se basa en una MÍNIMO de ...

(status, type_id, date_from) 

pero podría ampliarse para incluir el date_to y el precio también, pero no sé hasta qué punto el índice en ese nivel granular en realidad podría ayudar a

(status, type_id, date_from, date_to, price) 

EDITAR por comentarios

No debería necesitar todos esos índices individuales ... Sí, la clave principal en sí misma. Sin embargo, para los demás, debe tener índices compuestos basados ​​en cuáles podrían ser sus criterios de consulta comunes y eliminar los demás ... el motor podría confundirse sobre cuál podría ser el más adecuado para la consulta. Si sabe que siempre está buscando un determinado estado, tipo y fecha (suponiendo búsquedas de vehículos), hágalo como un índice. Si la consulta está buscando dicha información, pero también los precios dentro de ese criterio, ya estará muy cerca de los pocos registros indexados que califican y vuelan a través del precio como solo un criterio adicional.

Si ofrece consultas como transmisión solo automática frente a transmisión manual independientemente del año/marca, entonces sí, podría ser un índice propio. Sin embargo, si TÍPICAMENTE tiene algunos otros criterios "comunes", vuélvalo como un secundario que PUEDE ser utilizado en la consulta. Ejemplo: si busca transmisiones manuales de 2 puertas y 4 puertas, tenga su índice activado (transmission_id, category_id).

De nuevo, usted quiere lo que sea que ayude a reducir el campo de criterios basado en alguna condición "mínima". Si inserta una columna adicional en el índice que podría aplicarse "comúnmente", eso solo debería ayudar al rendimiento.

+0

No estoy familiarizado con los índices compuestos. Consulte mi publicación actualizada. ¿Mi indexación actual es ineficiente? – ChimeraTheory

+0

Simplemente agregue otro índice, pero en lugar de una columna ÚNICA, solo haga lo anterior, varias columnas separadas por columnas ... De esta manera, un índice puede tener múltiples componentes para que coincida más con los criterios de consulta. – DRapp

+0

Tengo 21 índices, ¿qué debo agrupar y qué debo hacer para eliminarlos primero? – ChimeraTheory

4

En lugar del cálculo costoso de la distancia precisa para todos de las filas usan un cuadro delimitador y calculan la distancia exacta solo para las filas dentro del cuadro.

El ejemplo más simple es calcular la longitud mínima/máxima y la latitud que le interese y agregarla a la cláusula WHERE. De esta forma, la distancia se calculará solo para un subconjunto de filas.

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

Para soluciones más complejas ver:

3

¿Es usted SQL más rápido sin esto?

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 

realizar ecuación matemática es muy caro

tal vez debería considerar una vista materializada que la pre-calcular que la distancia, y se puede seleccionar a partir de ese punto de vista. Dependiendo de cuán dinámicos sean sus datos, es posible que no tenga que actualizar sus datos con demasiada frecuencia.

+0

Un poco sí, pero necesito eso allí ... – ChimeraTheory

Cuestiones relacionadas