2009-05-22 8 views
10

Ok, quindi ho una tabella MySQL davvero mostruosa (900k records, 180 MB totali) e voglio estrarre dai sottogruppi i record con maggiore date_updated e calcolare ponderata media in ciascun gruppo. Il calcolo viene eseguito per ~ 15 ore, e ho la netta sensazione che io sia facendo torto.SQL magic: la query non dovrebbe richiedere 15 ore, ma è

Innanzitutto, layout di tabella mostruosa:

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

Solo chiave qui è in element_id (BTREE, ~ elementi 8k unici).

e calcolo processo:

Fai hash per ogni gruppo e sottogruppo.

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' 

Io davvero non capisco questo clamore con hash, ma ha funzionato veloce in questo modo. La magia oscura, presumo.

Trova massima data per ogni sottogruppo

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

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

registrazione temp1 con temp2 per trovare i valori medi ponderati per le categorie

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`; 

(ora che ho guardato attraverso di essa e scritto tutto giù, mi sembra che dovrei usare INNER JOIN in quest'ultima query (per evitare 900k * 900k temp table)).

Ancora, c'è un modo normale per farlo?

UPD: alcune foto per riferimento:

rimosso ImageShack morti collegamento

UPD: spiegare per soluzione proposta:

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

risposta

5

Utilizzando hashses è uno dei modi in cui un motore di database in grado di eseguire un join. Dovrebbe essere molto raro che tu debba scrivere il tuo join basato sull'hash; questo certamente non sembra uno di loro, con una tabella di file 900k con alcuni aggregati.

Sulla base di un commento, questa query potrebbe fare quello che stai cercando:

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 

GROUP BY esegue i calcoli per fonte + categoria + elemento.

Il JOIN è lì per filtrare le voci precedenti. Cerca le voci successive, quindi l'istruzione WHERE filtra le righe per le quali esiste una voce successiva. Un join come questo beneficia di un indice su (source_prefix, source_name, element_id, date_updated).

Ci sono molti modi per filtrare vecchie voci, ma questo tende a svolgere resonably bene.

+0

Ok, proverò a spiegare. Ci sono misurazioni in questa tabella. Ogni misura ha origine (identificata da prefisso + nome) e categoria. Ogni elemento può avere misurazioni in tutte le categorie o solo in alcune. Quello che voglio fare è trovare l'ultima misura per elemento da una fonte, quindi calcolare la media ponderata per elementi + categorie. Ci scusiamo per il mio inglese, btw - non la mia lingua principale: \ –

+0

Post updated. Il valore data_updated * esattamente * è uguale per tutte le ultime misurazioni? O sono proprio nello stesso giorno? – Andomar

+0

Sono solo gli ultimi per la stessa fonte ed elemento. Possono variare. –

3

Ok, quindi 900K righe isn' Un tavolo enorme, è abbastanza grande ma e le tue domande non dovrebbero durare a lungo.

Per prima cosa, quale delle 3 affermazioni sopra sta prendendo più tempo?

Il primo problema che vedo è con la vostra prima query.La tua clausola WHERE non include una colonna indicizzata. Questo significa che deve eseguire una scansione completa della tabella sull'intera tabella.

creare un indice sulla colonna "data_updated", quindi eseguire nuovamente la query e vedere che cosa che fa per voi.

Se non è necessario l'hash del e utilizza solo loro di avvalersi della magia nera poi rimuoverli completamente.

Edit: qualcuno con più SQL-fu di me probabilmente ridurre tutta la serie di logica in un'istruzione SQL senza l'uso delle tabelle temporanee.

Edit: My SQL è un po 'arrugginito, ma stai unendo due volte nel terzo staement SQL? Forse non farà la differenza, ma non dovrebbe essere:

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

Ultimo. Il primo è quasi istantaneo, il secondo è di circa 23 minuti. –

+0

Posso rimuovere gli hash ma poi la query impiegherà un tempo infinito (ok, forse no, ma non ho tanta pazienza, né i client). Suppongo che questi hash possano essere fatti negli indici in qualche modo. –

+0

Non pensare che la proposta indicizzata abbia senso. Una query aggregata come questa risulterà sempre in una scansione completa della tabella. – Andomar

Problemi correlati