如何提高总和列的性能

时间:2016-06-04 10:48:21

标签: mysql sql query-performance correlated-subquery

我正在尝试调整我的查询,但我不能再进一步了。有没有机会调整更多这个查询?特别是SUM子查询。

INDEX:db_prices.date

原始查询示例:

SELECT
db_villas.id,
db_villas.title1,
db_specials.id AS sid,
db_specials.title1 AS stitle,
db_cities.name AS cityName,
db_counties.name AS countyName,
db_assets.path,
db_villas.bathroom,
db_villas.bedroom,
db_villas.guest,
db_prices.date,
(SELECT SUM(db_prices.price) FROM db_prices WHERE db_prices.date BETWEEN "2016-08-01" AND "2016-09-30" AND db_prices.villaId=db_villas.id) AS price
FROM
db_villas
INNER JOIN db_cities ON db_villas.cityId = db_cities.id
LEFT OUTER JOIN db_specials ON db_villas.specialId = db_specials.id
INNER JOIN db_counties ON db_counties.cityid = db_cities.id AND db_villas.countyId = db_counties.id
INNER JOIN db_assets ON db_assets.guid = db_villas.guid
INNER JOIN db_villafacilities ON db_villafacilities.villaId = db_villas.id
INNER JOIN db_prices ON db_prices.villaId = db_villas.id
WHERE db_prices.date BETWEEN "2016-08-01" AND "2016-09-30" AND db_assets.isMainImage=1 AND db_villas.minRent <= 7
GROUP BY db_villas.id
HAVING (SELECT COUNT(*) FROM db_prices WHERE date BETWEEN "2016-08-01" AND "2016-09-30" AND db_prices.isFree = 0 AND db_prices.villaId = db_villas.id)=0

上面的查询在1.2秒内执行。

当我删除

(SELECT SUM(db_prices.price) FROM db_prices WHERE db_prices.date BETWEEN "2016-08-01" AND "2016-09-30" AND db_prices.villaId=db_villas.id) AS price

子查询执行时间减少到0.009秒。

如果我只是删除这部分

AND db_prices.villaId=db_villas.id

从子查询中它仍然在0.009秒内执行。

4 个答案:

答案 0 :(得分:3)

MySQL(自v 5.7起)有一个查询计划器,没有用于转换依赖子查询的chop

 (SELECT SUM(db_prices.price) 
    FROM db_prices
   WHERE db_prices.date BETWEEN "2016-08-01" AND "2016-09-30"
     AND db_prices.villaId=db_villas.id) AS price

进入可连接的独立子查询。这意味着查询计划程序会使用正常运行时间多次运行该查询。所以你需要自己改造它。独立子查询将如下所示:

                 SELECT villaId, 
                        SUM(price) price,  
                        SUM(CASE WHEN isFree = 0 THEN 1 ELSE 0 END) not_free_count
                   FROM db_prices
                  WHERE date BETWEEN  '2016-08-01' AND '2016-09-30'
                  GROUP BY villaId

此查询为您提供每个别墅的价格总和以及未标记为isFree的别墅数量。这很方便,因为您现在可以将其加入到表的其余部分。像这样:

 SELECT db_villas.id,
        db_villas.title1, etc etc,
        price_summary.price
   FROM db_villas
  INNER JOIN db_cities ON db_villas.cityId = db_cities.id
   LEFT OUTER JOIN db_specials ON db_villas.specialId = db_specials.id
           etc etc.
   LEFT JOIN (
                 SELECT villaId, 
                        SUM(price) price,  
                        SUM(CASE WHEN isFree = 0 THEN 1 ELSE 0 END) not_free_count
                   FROM db_prices
                  WHERE date BETWEEN  '2016-08-01' AND '2016-09-30'
                  GROUP BY villaId
       ) price_summary ON db_villas.villaId = price_summmary.villaId
 WHERE db_prices.date BETWEEN "2016-08-01" AND "2016-09-30"
   AND etc etc
   AND price_summary.not_free_count = 0
 GROUP BY db_villas.villaId

然后,您需要db_prices (date, villaId, price, isFree)上的复合索引来优化子查询。您可能还需要其他表的其他列的索引。

专业提示:在加快查询速度时,许多单列索引无法替代复合索引。单独索引大量列是一种常见且臭名昭着的反模式。阅读:http://use-the-index-luke.com/

专家提示:您的查询使用GROUP BY的非标准MySQL扩展。在MySQL的一个版本中,您可能很快就会停止工作,除非您更改某些服务器设置。阅读:https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html

答案 1 :(得分:2)

加快这个子查询:

(SELECT SUM(p.price)
 FROM db_prices p
 WHERE p.date BETWEEN '2016-08-01' AND '2016-09-30' AND 
       p.villaId = db_villas.id
) AS price

你想要一个索引。最佳索引是覆盖索引,这些列按此顺序排列:db_prices(villaId, date, price)。覆盖索引包括子查询中的所有列。

villaId应该是第一个,因为它具有相等的条件;然后date因为它也在where子句中。最后,price在索引中只是为了使处理更有效 - 所有列都在索引中,因此引擎不需要在数据页中查找值。

答案 2 :(得分:1)

首先尝试制作聚合并将其与表

连接
SELECT
db_villas.id,
db_villas.title1,
db_specials.id AS sid,
db_specials.title1 AS stitle,
db_cities.name AS cityName,
db_counties.name AS countyName,
db_assets.path,
db_villas.bathroom,
db_villas.bedroom,
db_villas.guest,
db_prices.date,
pricesum
FROM (SELECT db_prices.villaId, SUM(db_prices.price) as pricesum FROM db_prices WHERE db_prices.date BETWEEN "2016-08-01" AND "2016-09-30"  group by db_prices.villaId) as prices
INNER JOIN  db_villas ON prices.villaId = db_villas.id
...

有时这会有所帮助。

修改 纠正了一些复制错误

答案 3 :(得分:0)

创建一个多列(复合)索引,这将解决您的问题

要执行此操作,请按以下查询

create index <some_name> on db_prices(date,villaId);

如果您在解释性陈述中遇到进一步调查时面临更多问题