Волшебство SQL - запрос не должен занимать 15 часов, но он делает - PullRequest
10 голосов
/ 22 мая 2009

Хорошо, у меня есть одна действительно чудовищная таблица MySQL (900 тыс. Записей, всего 180 МБ), и я хочу извлечь из подгрупп записи с более высоким date_updated и вычислить средневзвешенное значение в каждой группе. Расчет длится ~ 15 часов, и я чувствую, что я делаю неправильно .

Первая, чудовищная раскладка стола:

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

Только ключ здесь находится на element_id (BTREE, ~ 8k уникальных элементов).

И процесс расчета:

Создать хэш для каждой группы и подгруппы.

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'

Я действительно не понимаю эту суету с хэшами, но так получилось быстрее. Темная магия, я полагаю.

Найти максимальную дату для каждой подгруппы

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

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

Соедините temp1 с temp2, чтобы найти средневзвешенные значения для категорий

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

(теперь, когда я просмотрел и записал все это, мне кажется, что я должен использовать INNER JOIN в последнем запросе (чтобы избежать 900k * 900k temp table)).

Тем не менее, есть ли нормальный способ сделать это?

UPD : некоторые изображения для справки:

удалена мертвая ссылка ImageShack

UPD : ОБЪЯСНИТЬ о предлагаемом решении:

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

Ответы [ 2 ]

5 голосов
/ 22 мая 2009

Использование хеш-кодов - один из способов, которым механизм базы данных может выполнить соединение. Это должно быть очень редко, когда вам нужно написать свое собственное соединение на основе хеша; это, конечно, не похоже на один из них, с таблицей строк в 900 тыс. с некоторыми агрегатами.

На основании вашего комментария этот запрос может сделать то, что вы ищете:

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 выполняет вычисления для источника + категория + элемент.

JOIN предназначен для фильтрации старых записей. Он ищет более поздние записи, а затем оператор WHERE отфильтровывает строки, для которых существует более поздняя запись. Подобное объединение получает выгоду от индекса (source_prefix, source_name, element_id, date_updated).

Есть много способов отфильтровать старые записи, но этот имеет тенденцию работать достаточно хорошо.

3 голосов
/ 22 мая 2009

Хорошо, так что 900K строк - это не массивная таблица, она достаточно большая, но ваши запросы действительно не должны занимать так много времени.

Перво-наперво, какое из 3 утверждений выше занимает больше всего времени?

Первая проблема, которую я вижу, связана с вашим первым запросом. Ваше предложение WHERE не включает индексированный столбец. Таким образом, это означает, что он должен выполнить полное сканирование всей таблицы.

Создайте индекс для столбца «data_updated», затем снова запустите запрос и посмотрите, что это для вас сделает.

Если вам не нужны хеш-коды и вы используете их только для использования темной магии, удалите их полностью.

Редактировать: Кто-то с большим количеством SQL-фу, чем я, вероятно, сведет весь ваш набор логики в один оператор SQL без использования временных таблиц.

Редактировать: Мой SQL немного ржавый, но вы дважды участвуете в третьем этапе SQL? Может быть, это не будет иметь значения, но не должно ли это быть:

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;

или

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