2009-05-22 6 views
10

Ok, entonces tengo una tabla MySQL realmente monstruosa (900k registros, 180 MB en total), y quiero extraer registros de subgrupos con mayor date_updated y calculo ponderado promedio en cada grupo. El cálculo se ejecuta durante ~ 15 horas, y tengo una fuerte sensación de que estoy haciéndolo mal.SQL mágico - la consulta no debería tomar 15 horas, pero sí

En primer lugar, diseño de la mesa monstruosa:

  • category
  • element_id
  • date_updated
  • value
  • weight
  • source_prefix
  • source_name

Sólo clave aquí está en element_id (BTREE, ~ elementos 8k únicas).

y cálculo de proceso:

picadillo Hacer para cada grupo y subgrupo.

CREATE TEMPORARY TABLE `temp1` (INDEX (`ds_hash`)) 
       SELECT `category`, 
       `element_id`, 
       `source_prefix`, 
       `source_name`, 
       `date_updated`, 
       `value`, 
       `weight`, 
       MD5(CONCAT(`category`, `element_id`, `source_prefix`, `source_name`)) AS `subcat_hash`, 
       MD5(CONCAT(`category`, `element_id`, `date_updated`)) AS `cat_hash` 
       FROM `bigbigtable` WHERE `date_updated` <= '2009-04-28' 

Realmente no entienden este alboroto con los hashes, pero funcionó más rápido de esta manera. La magia oscura, supongo.

Encuentra la fecha máxima para cada subgrupo

CREATE TEMPORARY TABLE `temp2` (INDEX (`subcat_hash`)) 

       SELECT MAX(`date_updated`) AS `maxdate` , `subcat_hash` 
       FROM `temp1` 
       GROUP BY `subcat_hash`; 

Ingreso Temp 1 con temp2 para encontrar valores medios ponderados para las categorías

CREATE TEMPORARY TABLE `valuebycats` (INDEX (`category`)) 
      SELECT `temp1`.`element_id`, 
        `temp1`.`category`, 
        `temp1`.`source_prefix`, 
        `temp1`.`source_name`, 
        `temp1`.`date_updated`, 
        AVG(`temp1`.`value`) AS `avg_value`, 
      SUM(`temp1`.`value` * `temp1`.`weight`)/SUM(`weight`) AS `rating` 

      FROM `temp1` LEFT JOIN `temp2` ON `temp1`.`subcat_hash` = `temp2`.`subcat_hash` 
      WHERE `temp2`.`subcat_hash` = `temp1`.`subcat_hash` 
      AND `temp1`.`date_updated` = `temp2`.`maxdate` 

      GROUP BY `temp1`.`cat_hash`; 

(ahora que me veía a través de él y escribió todo abajo, me parece que debería usar INNER JOIN en esa última consulta (para evitar la tabla de 900k * 900k temp)).

Aún así, ¿hay una forma normal para hacerlo?

UPD: un cierto cuadro para la referencia:

eliminado ImageShack muerto enlace

UPD: Vamos para solución propuesta:

+----+-------------+-------+------+---------------+------------+---------+--------------------------------------------------------------------------------------+--------+----------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref                     | rows | filtered | Extra          | 
+----+-------------+-------+------+---------------+------------+---------+--------------------------------------------------------------------------------------+--------+----------+----------------------------------------------+ 
| 1 | SIMPLE  | cur | ALL | NULL   | NULL  | NULL | NULL                     | 893085 | 100.00 | Using where; Using temporary; Using filesort | 
| 1 | SIMPLE  | next | ref | prefix  | prefix  | 1074 | bigbigtable.cur.source_prefix,bigbigtable.cur.source_name,bigbigtable.cur.element_id |  1 | 100.00 | Using where         | 
+----+-------------+-------+------+---------------+------------+---------+--------------------------------------------------------------------------------------+--------+----------+----------------------------------------------+  

Respuesta

5

Usando hashses es una de las maneras en las que un motor de base de datos puede ejecutar una combinación. Debería ser muy raro que tuvieras que escribir tu propia unión basada en hash; esto ciertamente no se ve como uno de ellos, con una tabla de 900k filas con algunos agregados.

Basado en su comentario, esta consulta podría hacer lo que busca:

SELECT cur.source_prefix, 
     cur.source_name, 
     cur.category, 
     cur.element_id, 
     MAX(cur.date_updated) AS DateUpdated, 
     AVG(cur.value) AS AvgValue, 
     SUM(cur.value * cur.weight)/SUM(cur.weight) AS Rating 
FROM eev0 cur 
LEFT JOIN eev0 next 
    ON next.date_updated < '2009-05-01' 
    AND next.source_prefix = cur.source_prefix 
    AND next.source_name = cur.source_name 
    AND next.element_id = cur.element_id 
    AND next.date_updated > cur.date_updated 
WHERE cur.date_updated < '2009-05-01' 
AND next.category IS NULL 
GROUP BY cur.source_prefix, cur.source_name, 
    cur.category, cur.element_id 

El GRUPO POR realiza los cálculos por la fuente, categoría y elemento.

El JOIN está ahí para filtrar las entradas antiguas. Busca las entradas posteriores y luego la instrucción WHERE filtra las filas para las cuales existe una entrada posterior. Una combinación como esta se beneficia de un índice en (prefijo_fuente, nombre_fuente, id_elemento, fecha_actualizada).

Hay muchas maneras de filtrar las entradas antiguas, pero esta tiende a funcionar de manera resonable.

+0

Está bien, intentaré explicarlo. Hay medidas en esta tabla. Cada medición tiene fuente (identificada por prefijo + nombre) y categoría. Cada elemento puede tener medidas en todas las categorías, o solo en algunas. Lo que quiero hacer es encontrar la última medición para el elemento de una fuente, luego calcular el promedio ponderado de los elementos + categorías. Lo siento por mi inglés, por cierto, no es mi idioma principal: \ –

+0

Publicada actualización. ¿La fecha_updated * exactamente * es igual para todas las últimas mediciones? ¿O solo están en el mismo día? – Andomar

+0

Son los últimos para la misma fuente y elemento. Ellos pueden variar –

3

Ok, así 900K filas ISN' Es una mesa masiva, es bastante grande, pero tus consultas no deberían demorar tanto.

Lo primero es lo primero, ¿cuál de las 3 afirmaciones anteriores está tomando más tiempo?

El primer problema que veo es con su primera consulta.Su cláusula WHERE no incluye una columna indexada. Entonces esto significa que tiene que hacer un escaneo completo de la tabla en toda la mesa.

Cree un índice en la columna "data_updated", luego vuelva a ejecutar la consulta y vea lo que hace por usted.

Si no necesitas los hash y solo los estás usando para aprovechar la magia oscura, retíralos por completo.

Editar: Alguien con más SQL-fu que yo probablemente reducirá todo su conjunto de lógica en una sola instrucción SQL sin el uso de tablas temporales.

Editar: Mi SQL está un poco oxidado, pero ¿se está uniendo dos veces en el tercer staement SQL? Tal vez no va a hacer una diferencia, pero no debería ser:

SELECT temp1.element_id, 
    temp1.category, 
    temp1.source_prefix, 
    temp1.source_name, 
    temp1.date_updated, 
    AVG(temp1.value) AS avg_value, 
    SUM(temp1.value * temp1.weight)/SUM(weight) AS rating 
FROM temp1 LEFT JOIN temp2 ON temp1.subcat_hash = temp2.subcat_hash 
WHERE temp1.date_updated = temp2.maxdate 
GROUP BY temp1.cat_hash; 

o

SELECT temp1.element_id, 
    temp1.category, 
    temp1.source_prefix, 
    temp1.source_name, 
    temp1.date_updated, 
    AVG(temp1.value) AS avg_value, 
    SUM(temp1.value * temp1.weight)/SUM(weight) AS rating 
FROM temp1 temp2 
WHERE temp2.subcat_hash = temp1.subcat_hash 
AND temp1.date_updated = temp2.maxdate 
GROUP BY temp1.cat_hash; 
+0

El último. Primero es casi instantáneo, segundo es aproximadamente 23 minutos. –

+0

Puedo eliminar hashes pero la consulta tomará un tiempo infinito (está bien, tal vez no, pero no tengo tanta paciencia ni los clientes). Supongo que estos hash se pueden convertir en índices de alguna manera. –

+0

No creo que la sugerencia del índice tenga sentido. Una consulta global como esta siempre dará como resultado una exploración completa de la tabla. – Andomar

Cuestiones relacionadas