Mysql组加入优化问题

时间:2015-03-15 21:04:31

标签: mysql database group-by query-optimization

我试图优化此查询,它会从building_rent_prices和building_weather返回多行,然后将它们分组并计算其字段的平均值。到目前为止,这些表都在一百万行以下,但它需要几秒钟,有谁知道我如何从复合索引优化这个或重写查询?我假设它应该可以是一个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 SIMPLE building_rent_prices范围

1 SIMPLE buildings eq_ref

1 SIMPLE building_weather ref

使用where;使用索引;使用临时;使用filesort

使用

使用where;使用索引

我正在处理一些测试数据,这是创建表

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;

4 个答案:

答案 0 :(得分:1)

首先,您对基于WEATHER的表的查询仅为4天,RENT PRICES表为10天。由于两者之间没有任何连接关联,因此每个建筑物ID将产生40个记录的笛卡尔结果。这是故意还是仅仅被认定为哎呀......

其次,我会调整查询,如下所示,但我也调整了两个天气和租金价格表以反映相同的日期范围。我首先通过构建和日期查询价格和组的子查询,然后加入到建筑物,然后按建筑物和日期分组的另一个子查询天气。但在这里,我从租金价格子查询加入到建筑物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子句(首先是日期),然后是GROUP BY(建筑物ID),并且是COVERING INDEX(包括租金)。同样地,对于建筑气象表也是出于同样的原因。

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天与每日加入的平均值,那么我已将“日期”作为组的一个组件删除,因此每个建筑物最多分别返回1个。

现在,以1:1:1的比例加入建筑物表将限制最终结果集中的记录。这应该照顾您对那些日子的平均值的关注。

答案 1 :(得分:1)

不要使用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)添加到第二个和第三个表格。

如果那些人没有解决它;回过头来修改查询和架构,我会更深入。

答案 2 :(得分:1)

让我们详细了解一下这个查询。您想要为每个建筑报告两种不同的平均值。您需要在单独的子查询中计算它们。如果你不这样做,你会得到笛卡尔组合爆炸。

一个是平均十一天的租金价格。您可以使用此子查询获取该数据:

          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表以生成最终结果集。

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_pricesbuilding_weather查询中提到的两个复合索引。

答案 3 :(得分:0)

对于任何遇到与我类似问题的人来说,解决方案是使用building_id对要加入的每个表进行分组,这样您就可以按平均值加入一对一。如果您不希望结果在所有表中都没有数据,那么使用JOIN而不是LEFT JOIN的Ollie Jones查询是最接近的答案。另外我遇到的主要问题是我忘了在avg(low_temp)列上放置一个索引,所以INDEXES。我从中学到的是,如果你在select中做了一个聚合函数,它就属于你的索引。我添加了low_temp。

building_weather(date,building_id,hi_temp,low_temp)AS由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