Question

Ok, donc j'ai une table MySQL vraiment monstrueuse (900K dossiers, 180 Mo au total), et je veux extraire des dossiers des sous-groupes avec date_updated supérieur et calculer la moyenne pondérée dans chaque groupe. Le calcul fonctionne pendant environ 15 heures, et j'ai un fort sentiment que je suis faire mal .

D'abord, la mise en page de table monstrueuse:

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

La clé n'est ici element_id (BTREE, ~ éléments uniques 8k).

processus de calcul:

faire de hachage pour chaque groupe et sous-groupe.

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'

Je ne comprends vraiment pas ce tapage avec hash, mais cela a fonctionné plus rapidement de cette façon. magie noire, je suppose.

Trouvez la date maximale pour chaque sous-groupe

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

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

Rejoindre temp1 avec temp2 pour trouver des valeurs moyennes pondérées pour les catégories

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

(maintenant que je regardais à travers elle et écrit tout cela, il me semble que je devrais utiliser INNER JOIN dans cette dernière requête (pour éviter 900k * 900k table temporaire)).

Toujours est-il un moyen normale pour le faire?

UPD : une image de référence:

lien mort ImageShack supprimé

UPD : EXPLIQUER pour la solution proposée:

+----+-------------+-------+------+---------------+------------+---------+--------------------------------------------------------------------------------------+--------+----------+----------------------------------------------+
| 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                                  |
+----+-------------+-------+------+---------------+------------+---------+--------------------------------------------------------------------------------------+--------+----------+----------------------------------------------+    
Était-ce utile?

La solution

Utilisation hashses est l'une des façons dont un moteur de base de données peut exécuter une jointure. Il devrait être très rare que vous devriez écrire votre propre base de jointure de hachage; cela ne ressemble certainement pas à l'un d'eux, avec une table 900K de lignes avec des agrégats.

Sur la base de votre commentaire, cette requête pourrait faire ce que vous cherchez:

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 effectue les calculs par la source + catégorie + élément.

Le JOIN est là pour filtrer les anciennes entrées. Il cherche des entrées plus tard, puis l'instruction WHERE filtre les lignes pour lesquelles il existe une entrée plus tardive. Une jointure comme cette bénéficie d'un indice sur (source_prefix, nom_source, element_id, date_updated).

Il y a plusieurs façons de filtrer les anciennes entrées, mais celui-ci a tendance à effectuer resonably bien.

Autres conseils

Ok, donc 900K lignes n'est pas une table massive, il est assez grand, mais vos requêtes ne devrait vraiment pas prendre longtemps.

Tout d'abord, ce qui les 3 déclarations ci-dessus est de prendre le plus de temps?

Le premier problème que je vois est votre première requête. Votre clause WHERE ne comprend pas une colonne indexée. Cela signifie donc qu'il doit faire une analyse complète de la table sur la table entière.

Créer un index sur la colonne « data_updated », puis exécutez à nouveau la requête et voir ce que cela fait pour vous.

Si vous n'avez pas besoin seulement de la valeur de hachage et les utilisez pour profiter de la magie noire les supprimer alors complètement.

Edit: Quelqu'un avec plus SQL-fu que moi réduirai probablement votre ensemble de la logique dans une instruction SQL sans l'utilisation des tables temporaires.

Edit: My SQL est un peu rouillé, mais vous rejoignons deux fois dans le troisième staement SQL? Peut-être qu'il ne fera pas une différence, mais devrait-il pas:

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;

ou

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;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top