2010-09-30 18 views
7

long time lurker, primera pregunta!Mysql slow query: JOIN + multiple WHERES + ORDER POR

estoy luchando para optimizar esta consulta, que selecciona los artículos de precio más bajo que coinciden con los filtros elegidos:

SELECT product_info.*, MIN(product_all.sale_price) as sale_price, product_all.buy_link 
FROM product_info 
NATURAL JOIN (SELECT * FROM product_all WHERE product_all.date = '2010-09-30') as product_all 
WHERE (product_info.category = 2 
AND product_info.gender = 'W') 
GROUP BY product_all.prod_id 
ORDER BY MIN(product_all.sale_price) ASC LIMIT 13 

Su explico:

| id | select_type | table  | type | possible_keys            | key  | key_len | ref     | rows | Extra       | 
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
| 1 | PRIMARY  | <derived2> | ALL | NULL              | NULL | NULL | NULL    | 89801 | Using temporary; Using filesort | 
| 1 | PRIMARY  | product_info | eq_ref | PRIMARY,category_prod_id_retail_price,category_ret...  | PRIMARY | 4  | product_all.prod_id | 1  | Using where      | 
| 2 | DERIVED  | product_all | ref | date_2             | date_2 | 3  |      | 144107 |         | 

He intentado eliminar la sub consulta, la cual intuitivamente parece mejor, pero en la práctica lleva aún más tiempo:

SELECT product_info.*, MIN(product_all.sale_price) as sale_price, product_all.buy_link 
FROM product_info 
NATURAL JOIN product_all 
WHERE (product_all.date = '2010-09-30' 
AND product_info.category = 2 
AND product_info.gender = 'W') 
GROUP BY product_all.prod_id 
ORDER BY MIN(product_all.sale_price) ASC LIMIT 13 

Y su exp yacido:

| id | select_type | table  | type | possible_keys            | key      | key_len | ref        | rows | Extra          | 
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
| 1 | SIMPLE  | product_info | ref | PRIMARY,category_prod_id_retail_price,category_ret...  | category_retail_price | 5  | const        | 269 | Using where; Using temporary; Using filesort | 
| 1 | SIMPLE  | product_all | ref | PRIMARY,prod_id,date_2         | prod_id     | 4  | equipster_db.product_info.prod_id | 141 | Using where         | 

Estas son las tablas:

CREATE TABLE `product_all` (
`prod_id` INT(10) NOT NULL PRIMARY KEY , 
`ref_id` INT(10) NOT NULL PRIMARY KEY , 
`date` DATE NOT NULL , 
`buy_link` BLOB NOT NULL , 
`sale_price` FLOAT NOT NULL 
) ENGINE = MYISAM ; 


CREATE TABLE `product_info` (
`prod_id` INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY , 
`prod_name` VARCHAR(200) NOT NULL, 
`brand` VARCHAR(50) NOT NULL, 
`retail_price` FLOAT NOT NULL 
`category` INT(3) NOT NULL, 
`gender` VARCHAR(1) NOT NULL, 
`type` VARCHAR(10) NOT NULL 
) ENGINE = MYISAM ; 

Mis preguntas: estructura
-que consulta parece óptima?
¿Qué índices optimizarían esta consulta?
-menos importante: ¿cómo cambia el enfoque de indexación al añadir o eliminar cláusulas WHERE o el uso de un orden diferente BY, tales como la clasificación por% de descuento:

ORDER BY (1-(MIN(product_all.sale_price)/product_info.retail_price)) DESC 

edición: ambas consultas de reunión natural actúa sobre prod_id (un registro en product_info puede tener varias instancias en product_all, por lo que deben agruparse)

+0

uno de los PK's es compuesto, pero sí cada grupo es una fila: el ID del producto, el precio más bajo para ese producto y datos relacionados. editar: esta fue una respuesta a un comentario que parece haberse desvanecido. edit2: sí, creo que presioné editar en lugar de agregar comentario ... sin problemas. – chrisblanch

Respuesta

4

Los índices hacen una gran diferencia en mysql, una consulta que tomó 15 minutos con un conjunto incorrecto de índices tomó .2 segundos con los correctos, pero encontrar el equilibrio correcto que generalmente es el problema. Naturalmente, sin algunos datos de muestra es realmente difícil de decir si la siguiente solución le ahorrará tiempo, pero en teoría debería hacerlo.

para responder a sus preguntas, me gustaría rediseñar las tablas de este modo:

CREATE TABLE `product_all` ( 
`prod_id` INT(10) NOT NULL, 
`ref_id` INT(10) NOT NULL, 
`date` DATE NOT NULL , 
`buy_link` BLOB NOT NULL , 
`sale_price` FLOAT NOT NULL, 
PRIMARY KEY (prod_id, ref_id) , 
INDEX date_Index (`date` ASC), 
UNIQUE INDEX prod_price_Index (prod_id ASC, sale_price ASC) 
) ENGINE = MYISAM ; 


CREATE TABLE `product_info` ( 
`prod_id` INT(10) NOT NULL AUTO_INCREMENT, 
`prod_name` VARCHAR(200) NOT NULL, 
`brand` VARCHAR(50) NOT NULL, 
`retail_price` FLOAT NOT NULL, 
`category` INT(3) NOT NULL, 
`gender` VARCHAR(1) NOT NULL, 
`type` VARCHAR(10) NOT NULL, 
PRIMARY KEY (prod_id) , 
UNIQUE INDEX prod_id_name_Index (prod_id ASC, prod_name ASC), 
INDEX category_Index (category ASC), 
INDEX gender_Index (gender ASC) 
) ENGINE = MYISAM ; 

SELECT product_info.*, MIN(product_all.sale_price) as sale_price, product_all.buy_link   
FROM product_info   
NATURAL JOIN (SELECT * FROM product_all WHERE product_all.date = '2010-09-30') as product_all   
WHERE (product_info.category = 2   
AND product_info.gender = 'W')   
GROUP BY product_all.prod_id   
ORDER BY MIN(product_all.sale_price) ASC LIMIT 13   

La ganancia de rendimiento que aquí se gana mi indexación de los principales campos que se están uniendo sobre y se ofrecen en la cláusula where. Personalmente, iría con tu primera consulta, ya que cuando lo piensas, debería funcionar mejor.

Por lo que yo entiendo por qué está sucediendo en la primera y segunda consulta:

  • La primera consulta se está filtrando por una sub-consulta antes de hacer el unirse natural, eso significa que su único unirse en los datos resultantes y no toda la tabla.
  • La segunda consulta se une a la segunda tabla entera y luego filtrando las filas resultantes del lote completo a lo que desea.

Como regla general, normalmente desea agregar índices en sus principales campos de unión y también en los campos que usa más en las cláusulas where.También he puesto algunos índices únicos en algunos de los campos que querrá consultar regularmente, como prod_id_name_Index.

Si esto no mejora su rendimiento si pudiera publicar algunos datos ficticios para jugar, podría obtener una solución más rápida que pueda comparar.

Here es un artículo que pasa por la indexación de rendimiento en mysql, vale la pena leerlo si quiere saber más.

¡Buena suerte!

EDIT: su última pregunta que me perdí la primera vez, la respuesta es que si indexa los principales campos de unión, entonces cambia a donde solo afectará un poco el rendimiento general, pero los índices únicos que he puesto en las tablas debería representar la mayoría de las cosas en las que querrá basar consultas. Lo principal que debe recordar es que si consulta o se une a un campo con frecuencia, entonces realmente debería indexarse, pero las consultas menores y los cambios en el orden no deberían preocuparlo en términos de realinear su estrategia de indexación.

+0

jon, gracias! esos múltiples índices de columna hicieron el truco, y también su edición fue acertada, el orden por no estaba realmente arrastrando la consulta ya que solo opera en 13 filas. ¡aclamaciones! – chrisblanch

+0

Jon, nos ayudaste a lo grande. Esa parte del índice JOIN es algo de lo que nunca antes había oído hablar y era un salvavidas con un problema similar. – jerebear

+0

Siempre es bueno escuchar! Es una parte del diseño de la base de datos a menudo pasada por alto, que a veces puede costar caro, contento de ayudar. – JonVD

0

En cuanto al rendimiento, su nunca una buena cosa para el uso

select * 

usted debe utilizar los nombres de las columnas individuales en su lugar.

select column1,column2 etc... 
+0

palabra para eso ... una de las pocas cosas que sí sabía, pero pensé que era insignificante y mejora la legibilidad de mi pregunta. – chrisblanch

0

Personalmente soy un minimalista sql y evitar cualquier tipo de consultas sub o uniones que no puede ser índice de columnas de índice.

Si eso no es realmente posible, probablemente ejecutaré las subconsultas individualmente para recoger mis claves, ordenarlas en el sitio del cliente y luego crear una cláusula where (...) en la cláusula.

JohnVD tiene muchos puntos buenos, pero si va a necesitar crear una clave única que incluya nombre_producto, realmente debería ver si eso se puede normalizar en una.

Indicar columnas varchar es algo a lo que hay que alejarse a toda costa, si es posible. Cada entrada de índice es tan grande como el tamaño máximo de la columna, incluso si por lo general solo es una fracción de eso. Y si está usando un conjunto de caracteres como utf-8, entonces el tamaño es ~ maxlen + 3.

Con su límite parece que es necesario realizar el pedido. Pero al igual que un FYI cuando haces un grupo, si vas a consumir todo el conjunto de resultados, agrega un ORDER BY NULL. Ejecute las dos variantes a través de la explicación para ver por qué; el orden por nulo elimina una lista de archivos implícita y puede clasificar el lado del cliente. (Sin embargo, esto no es posible si está realizando un agrupamiento con rollup)

0

Debe quedarse con la segunda consulta. Use un índice en la columna que reduzca las filas afectadas más. En este caso, podría ser la fecha. si las condiciones del filtro siempre contienen más de una columna, debe intentar con un índice de varias columnas. MySQL solo usará un índice.

0

Como dijo Mitch, tratar de encontrar los criterios que naturalmente tendrían el recuento de registros más bajo definitivamente ganaría para el rendimiento. Y si Category + Gender fuera muy común, hágalo en AMBAS columnas. Además, una vez que encuentre los criterios óptimos, puede alterar la siguiente consulta para que coincida mejor. El "STRAIGHT_JOIN" le dice a MySQL que lo haga en el orden que usted indique en lugar de tratar de cambiar la tabla principal utilizada para consultar y unir a la otra ... Por lo tanto, no sé cuál es más exacto del índice de categoría , sexo o fecha ...Si Date va a tener menos bases de registro, entonces cambiaría ESO como la primera tabla en la cláusula FROM, y movería mentalmente los criterios de TI en la fecha a la primera posición de la cláusula WHERE (solo yo personalmente para mantenerme sincronizado con las tablas visualmente) He visto que STRAIGHT_JOIN mejora el rendimiento significativamente en MUCHAS situaciones que, de lo contrario, parecían simples consultas.

SELECT STRAIGHT_JOIN 
     product_info.*, 
     MIN(product_all.sale_price) as sale_price, 
     product_all.buy_link 
    FROM 
     product_info, 
     product_all 
    where 
      product_info.category = 2 
     AND product_info.gender = 'W' 
     and product_info.prod_id = product_all.prod_id 
     AND product_all.date = '2010-09-30' 
    GROUP BY 
     product_info.prod_id 
    ORDER BY 
     MIN(product_all.sale_price) ASC 
    LIMIT 13