好的,所以我有一个非常可怕的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`;
使用temp2加入temp1以查找类别的加权平均值
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 |
+----+-------------+-------+------+---------------+------------+---------+--------------------------------------------------------------------------------------+--------+----------+----------------------------------------------+
答案 0 :(得分: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)上的索引。
有许多方法可以过滤掉旧条目,但是这个方法往往表现得非常好。
答案 1 :(得分: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;