Mysql 群组加入优化问题
Mysql Group Join Optimization Issue
我正在尝试优化此查询 returns 来自 building_rent_prices 和 building_weather 的多行,然后对它们进行分组并计算它们字段的平均值。到目前为止,table 都在一百万行以下,但它需要几秒钟,有谁知道我如何从复合索引优化它或重写查询?我假设它应该能够成为 100 毫秒或更快的查询,但到目前为止它似乎不能
SELECT b.*
, AVG(r.rent)
, AVG(w.high_temp)
FROM buildings b
LEFT
JOIN building_rent_prices r
ON r.building_id = b.building_id
LEFT
JOIN building_weather w
ON w.building_id = b.building_id
WHERE w.date BETWEEN CURDATE() AND CURDATE + INTERVAL 4 DAY
AND r.date BETWEEN CURDATE() AND CURDATE + INTERVAL 10 day
GROUP
BY b.building_id
ORDER
BY AVG(r.rent) / b.square_feet DESC
LIMIT 10;
解释如下:
1 简单 building_rent_prices 范围
1 栋简单建筑 eq_ref
1 简单 building_weather 参考
在哪里使用;使用索引;使用临时的;使用文件排序
使用哪里
在哪里使用;使用索引
我正在处理一些测试数据,这里是创建 table
CREATE TABLE building(
building_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
square_feet INT
);
CREATE TABLE building_weather(
building_weather_id INT PRIMARY KEY AUTO_INCREMENT,
building_id INT,
weather_date DATE,
high_temp INT
);
CREATE TABLE building_rates(
building_rate_id INT PRIMARY KEY AUTO_INCREMENT,
building_id INT,
weather_date DATE,
rate double
);
ALTER TABLE building_rates INDEX(building_id);
ALTER TABLE buildings INDEX(building_id);
ALTER TABLE building_weather INDEX(building_id);
根据 DRapp 没有索引的回答,这似乎在 1 秒内完成(我仍然需要测试它是否有效)
select
B.*,
BRP.avgRent,
BW.avgTemp
from
( select building_id,
AVG( rent ) avgRent
from
building_rent_prices
where
date BETWEEN CURDATE() AND CURDATE() + 10
group by
building_id
order by
building_id ) BRP
JOIN buildings B
on BRP.building_id = B.building_id
left join ( select building_id,
AVG( hi_temp ) avgTemp
from building_weather
where date BETWEEN CURDATE() AND CURDATE() + 10
group by building_id) BW
on BRP.building_id = BW.building_id
GROUP BY BRP.building_id
ORDER BY BRP.avgRent / 1 DESC
LIMIT 10;
首先,您查询的 WEATHER based table 只有 4 天,RENT PRICES table 是 10 天。由于两者之间没有任何连接相关性,因此您将得到每个建筑物 ID 有 40 条记录的笛卡尔结果。那是故意的还是只是没有被识别为哎呀...
其次,我会像下面那样调整查询,而且,我还调整了天气和租金 tables 以反映相同的日期范围。我从仅价格的子查询开始,并按建筑物和日期分组,然后加入建筑物,然后是另一个子查询以按建筑物和日期分组的天气。但是在这里,我从租金价格子查询加入了建筑物 ID 和日期的天气子查询,因此它最多保留 1:1 比率。我不知道为什么天气甚至是跨日期范围的考虑因素。
但是为了帮助索引,我建议如下
Table Index on
buildings (Building_ID) <-- probably already exists as a PK
building_rent_prices (date, building_id, rent)
building_weather (date, building_id, hi_temp)
索引的目的是利用WHERE子句(date first),THEN GROUP BY(building ID),是一个COVERING INDEX(包括租金)。出于同样的原因,对于建筑天气 table 也是如此。
select
B.*,
BRP.avgRent,
BW.avgTemp
from
( select building_id,
AVG( rent ) avgRent
from
building_rent_prices
where
date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
group by
building_id
order by
building_id ) BRP
JOIN buildings B
on BRP.building_id = B.building_id
left join ( select building_id,
AVG( hi_temp ) avgTemp
from
building_weather
where
date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
group by
building_id ) BW
on BRP.building_id = BW.building_id
GROUP BY
BRP.building_id
ORDER BY
BRP.avgRent / B.square_feet DESC
LIMIT 10;
澄清...
我不能保证执行顺序,但本质上,BPR 和 BW 别名的两个(查询),它们将在任何连接发生之前快速完成并执行。如果你想要(在我的例子中)10 天与每天加入的平均值,那么我已经删除了 "date" 作为组的一个组成部分,所以每个人最多分别 return,每栋楼 1 个。
现在,仅以 1:1:1 的比例加入建筑物 table 将限制最终结果集中的记录。这应该可以解决您对相关日期的平均值的担忧。
不要使用 CURDATE + 4:
mysql> select CURDATE(), CURDATE() + 30, CURDATE() + INTERVAL 30 DAY;
+------------+----------------+-----------------------------+
| CURDATE() | CURDATE() + 30 | CURDATE() + INTERVAL 30 DAY |
+------------+----------------+-----------------------------+
| 2015-03-15 | 20150345 | 2015-04-14 |
+------------+----------------+-----------------------------+
将INDEX(building_id)
添加到第二个和第三个表中。
如果这些都没有解决;返回修改后的查询和模式,我会深入研究。
让我们详细看一下这个查询。您想要报告每栋建筑的两种不同类型的平均值。您需要在单独的子查询中计算它们。如果你不这样做,你将得到笛卡尔组合爆炸。
一个是十一天的租金价格的平均值。您使用此子查询获取该数据:
SELECT building_id, AVG(rent) rent
FROM building_rent_prices
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
GROUP BY building_id
此子查询可以通过 building_rent_prices
上的 compound covering index 优化,由 (date, building_id, rent)
组成。
接下来是五天的平均气温。
SELECT building_id, AVG(high_temp) high_temp
FROM building_weather
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 4 DAY
GROUP BY building_id
这可以通过 building_weather
上的复合覆盖索引进行优化,该索引由 (date, building_id, high_temp)
.
组成
最后,您需要将这两个子查询连接到您的 buildings
table 以生成最终结果集。
SELECT buildings.*, a.rent, b.high_temp
FROM buildings
LEFT JOIN (
SELECT building_id, AVG(rent) rent
FROM building_rent_prices
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
GROUP BY building_id
) AS a ON buildings.building_id = a.building_id
LEFT JOIN (
SELECT building_id, AVG(high_temp) high_temp
FROM building_weather
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 4 DAY
GROUP BY building_id
) AS b ON buildings.building_id = b.building_id
ORDER BY a.rent / buildings.square_feet DESC
LIMIT 10
一旦优化了两个子查询,这个子查询除了 building_id
主键外不需要任何东西。
总而言之,要加快此查询的速度,请创建 building_rent_prices
和 building_weather
查询中提到的两个复合索引。
对于遇到与我类似问题的任何人,解决方案是使用 building_id 对每个 table 您想加入的人进行分组,这样您就可以一对一地加入每个平均值。如果您不希望所有 table 中都没有数据的结果,使用 JOIN 而不是 LEFT JOIN 的 Ollie Jones 查询是最接近的答案。另外我遇到的主要问题是我忘记在 avg(low_temp) 列上放置索引,所以 INDEXES.我从中学到的是,如果您在 select 中执行聚合函数,它就属于您的索引。我添加了 low_temp 到它。
building_weather(日期,building_id,hi_temp,low_temp)按照 Ollie 和 DR APP
的建议
ALTER TABLE building_weather ADD index(date, building_id, hi_temp, low_temp);
SELECT buildings.*, a.rent, b.high_temp, b.low_temp
FROM buildings
JOIN (
SELECT building_id, AVG(rent) rent
FROM building_rent_prices
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
GROUP BY building_id
) AS a ON buildings.building_id = a.building_id
JOIN (
SELECT building_id, AVG(high_temp) high_temp, AVG(low_temp) low_temp
FROM building_weather
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 4 DAY
GROUP BY building_id
) AS b ON buildings.building_id = b.building_id
ORDER BY a.rent / buildings.square_feet DESC
LIMIT 10
我正在尝试优化此查询 returns 来自 building_rent_prices 和 building_weather 的多行,然后对它们进行分组并计算它们字段的平均值。到目前为止,table 都在一百万行以下,但它需要几秒钟,有谁知道我如何从复合索引优化它或重写查询?我假设它应该能够成为 100 毫秒或更快的查询,但到目前为止它似乎不能
SELECT b.*
, AVG(r.rent)
, AVG(w.high_temp)
FROM buildings b
LEFT
JOIN building_rent_prices r
ON r.building_id = b.building_id
LEFT
JOIN building_weather w
ON w.building_id = b.building_id
WHERE w.date BETWEEN CURDATE() AND CURDATE + INTERVAL 4 DAY
AND r.date BETWEEN CURDATE() AND CURDATE + INTERVAL 10 day
GROUP
BY b.building_id
ORDER
BY AVG(r.rent) / b.square_feet DESC
LIMIT 10;
解释如下:
1 简单 building_rent_prices 范围
1 栋简单建筑 eq_ref
1 简单 building_weather 参考
在哪里使用;使用索引;使用临时的;使用文件排序
使用哪里
在哪里使用;使用索引
我正在处理一些测试数据,这里是创建 table
CREATE TABLE building(
building_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
square_feet INT
);
CREATE TABLE building_weather(
building_weather_id INT PRIMARY KEY AUTO_INCREMENT,
building_id INT,
weather_date DATE,
high_temp INT
);
CREATE TABLE building_rates(
building_rate_id INT PRIMARY KEY AUTO_INCREMENT,
building_id INT,
weather_date DATE,
rate double
);
ALTER TABLE building_rates INDEX(building_id);
ALTER TABLE buildings INDEX(building_id);
ALTER TABLE building_weather INDEX(building_id);
根据 DRapp 没有索引的回答,这似乎在 1 秒内完成(我仍然需要测试它是否有效)
select
B.*,
BRP.avgRent,
BW.avgTemp
from
( select building_id,
AVG( rent ) avgRent
from
building_rent_prices
where
date BETWEEN CURDATE() AND CURDATE() + 10
group by
building_id
order by
building_id ) BRP
JOIN buildings B
on BRP.building_id = B.building_id
left join ( select building_id,
AVG( hi_temp ) avgTemp
from building_weather
where date BETWEEN CURDATE() AND CURDATE() + 10
group by building_id) BW
on BRP.building_id = BW.building_id
GROUP BY BRP.building_id
ORDER BY BRP.avgRent / 1 DESC
LIMIT 10;
首先,您查询的 WEATHER based table 只有 4 天,RENT PRICES table 是 10 天。由于两者之间没有任何连接相关性,因此您将得到每个建筑物 ID 有 40 条记录的笛卡尔结果。那是故意的还是只是没有被识别为哎呀...
其次,我会像下面那样调整查询,而且,我还调整了天气和租金 tables 以反映相同的日期范围。我从仅价格的子查询开始,并按建筑物和日期分组,然后加入建筑物,然后是另一个子查询以按建筑物和日期分组的天气。但是在这里,我从租金价格子查询加入了建筑物 ID 和日期的天气子查询,因此它最多保留 1:1 比率。我不知道为什么天气甚至是跨日期范围的考虑因素。
但是为了帮助索引,我建议如下
Table Index on
buildings (Building_ID) <-- probably already exists as a PK
building_rent_prices (date, building_id, rent)
building_weather (date, building_id, hi_temp)
索引的目的是利用WHERE子句(date first),THEN GROUP BY(building ID),是一个COVERING INDEX(包括租金)。出于同样的原因,对于建筑天气 table 也是如此。
select
B.*,
BRP.avgRent,
BW.avgTemp
from
( select building_id,
AVG( rent ) avgRent
from
building_rent_prices
where
date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
group by
building_id
order by
building_id ) BRP
JOIN buildings B
on BRP.building_id = B.building_id
left join ( select building_id,
AVG( hi_temp ) avgTemp
from
building_weather
where
date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
group by
building_id ) BW
on BRP.building_id = BW.building_id
GROUP BY
BRP.building_id
ORDER BY
BRP.avgRent / B.square_feet DESC
LIMIT 10;
澄清...
我不能保证执行顺序,但本质上,BPR 和 BW 别名的两个(查询),它们将在任何连接发生之前快速完成并执行。如果你想要(在我的例子中)10 天与每天加入的平均值,那么我已经删除了 "date" 作为组的一个组成部分,所以每个人最多分别 return,每栋楼 1 个。
现在,仅以 1:1:1 的比例加入建筑物 table 将限制最终结果集中的记录。这应该可以解决您对相关日期的平均值的担忧。
不要使用 CURDATE + 4:
mysql> select CURDATE(), CURDATE() + 30, CURDATE() + INTERVAL 30 DAY;
+------------+----------------+-----------------------------+
| CURDATE() | CURDATE() + 30 | CURDATE() + INTERVAL 30 DAY |
+------------+----------------+-----------------------------+
| 2015-03-15 | 20150345 | 2015-04-14 |
+------------+----------------+-----------------------------+
将INDEX(building_id)
添加到第二个和第三个表中。
如果这些都没有解决;返回修改后的查询和模式,我会深入研究。
让我们详细看一下这个查询。您想要报告每栋建筑的两种不同类型的平均值。您需要在单独的子查询中计算它们。如果你不这样做,你将得到笛卡尔组合爆炸。
一个是十一天的租金价格的平均值。您使用此子查询获取该数据:
SELECT building_id, AVG(rent) rent
FROM building_rent_prices
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
GROUP BY building_id
此子查询可以通过 building_rent_prices
上的 compound covering index 优化,由 (date, building_id, rent)
组成。
接下来是五天的平均气温。
SELECT building_id, AVG(high_temp) high_temp
FROM building_weather
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 4 DAY
GROUP BY building_id
这可以通过 building_weather
上的复合覆盖索引进行优化,该索引由 (date, building_id, high_temp)
.
最后,您需要将这两个子查询连接到您的 buildings
table 以生成最终结果集。
SELECT buildings.*, a.rent, b.high_temp
FROM buildings
LEFT JOIN (
SELECT building_id, AVG(rent) rent
FROM building_rent_prices
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
GROUP BY building_id
) AS a ON buildings.building_id = a.building_id
LEFT JOIN (
SELECT building_id, AVG(high_temp) high_temp
FROM building_weather
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 4 DAY
GROUP BY building_id
) AS b ON buildings.building_id = b.building_id
ORDER BY a.rent / buildings.square_feet DESC
LIMIT 10
一旦优化了两个子查询,这个子查询除了 building_id
主键外不需要任何东西。
总而言之,要加快此查询的速度,请创建 building_rent_prices
和 building_weather
查询中提到的两个复合索引。
对于遇到与我类似问题的任何人,解决方案是使用 building_id 对每个 table 您想加入的人进行分组,这样您就可以一对一地加入每个平均值。如果您不希望所有 table 中都没有数据的结果,使用 JOIN 而不是 LEFT JOIN 的 Ollie Jones 查询是最接近的答案。另外我遇到的主要问题是我忘记在 avg(low_temp) 列上放置索引,所以 INDEXES.我从中学到的是,如果您在 select 中执行聚合函数,它就属于您的索引。我添加了 low_temp 到它。
building_weather(日期,building_id,hi_temp,low_temp)按照 Ollie 和 DR APP
的建议ALTER TABLE building_weather ADD index(date, building_id, hi_temp, low_temp);
SELECT buildings.*, a.rent, b.high_temp, b.low_temp
FROM buildings
JOIN (
SELECT building_id, AVG(rent) rent
FROM building_rent_prices
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
GROUP BY building_id
) AS a ON buildings.building_id = a.building_id
JOIN (
SELECT building_id, AVG(high_temp) high_temp, AVG(low_temp) low_temp
FROM building_weather
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 4 DAY
GROUP BY building_id
) AS b ON buildings.building_id = b.building_id
ORDER BY a.rent / buildings.square_feet DESC
LIMIT 10