10

好的,所以我有一个非常可怕的 MySQL 表(900k 记录,总共 180 MB),我想从子组中提取更高的记录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 临时表))。

不过,有没有正常的方法可以做到这一点?

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

2 回答 2

5

使用哈希是数据库引擎可以执行连接的方式之一。您必须编写自己的基于哈希的连接应该是非常罕见的;这肯定不像其中之一,它有一个包含一些聚合的 900k 行表。

根据您的评论,此查询可能会满足您的需求:

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)上的索引。

有很多方法可以过滤掉旧条目,但这种方法往往表现得相当不错。

于 2009-05-22T10:26:30.037 回答
3

好的,所以 900K 行并不是一个庞大的表,它相当大,但是您的查询确实不应该花那么长时间。

首先,上面 3 个语句中哪一个花费的时间最多?

我看到的第一个问题是您的第一个查询。您的 WHERE 子句不包含索引列。所以这意味着它必须对整个表进行全表扫描。

在“data_updated”列上创建一个索引,然后再次运行查询,看看它对您有什么作用。

如果您不需要哈希并且仅使用它们来利用黑魔法,那么将它们完全删除。

编辑:比我有更多 SQL-fu 的人可能会在不使用临时表的情况下将你的整个逻辑集简化为一个 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;
于 2009-05-22T10:19:27.540 回答