2016-11-25 110 views
-1

我想优化MySQL查询。我正在尝试使用每15分钟针对特定商店的物品价格的移动平均值更新表格列。优化MySQL查询 - 使用索引

我的表具有下面的结构

╔═════╦═════════════════════╦════════════╦══════╦════════════════╗ 
║ ID ║  DATETIME  ║ NAME  ║Price ║ 15_MIN_AVERAGE ║ 
╠═════╬═════════════════════╬════════════╬══════╬════════════════╣ 
║ 1 ║ 2000-01-01 00:00:05 ║ WALMART ║ 1 ║    ║ 
║ 2 ║ 2000-01-01 00:00:05 ║ BESTBUY ║ 6 ║    ║ 
║ 3 ║ 2000-01-01 00:00:05 ║ RADIOSHACK ║ 2 ║    ║ 
║ 4 ║ 2000-01-01 00:00:10 ║ WALMART ║ 6 ║    ║ 
║ 5 ║ 2000-01-01 00:00:10 ║ BESTBUY ║ 2 ║    ║ 
║ 6 ║ 2000-01-01 00:00:10 ║ RADIOSHACK ║ 8 ║    ║ 
║ 7 ║ 2000-01-01 00:00:15 ║ WALMART ║ 10 ║    ║ 
║ 8 ║ 2000-01-01 00:00:15 ║ BESTBUY ║ 2 ║    ║ 
║ 9 ║ 2000-01-01 00:00:15 ║ RADIOSHACK ║ 3 ║    ║ 
║ 10 ║ 2000-01-01 00:00:20 ║ WALMART ║ 6 ║    ║ 
║ 11 ║ 2000-01-01 00:00:20 ║ BESTBUY ║ 4 ║    ║ 
║ 12 ║ 2000-01-01 00:00:20 ║ RADIOSHACK ║ 5 ║    ║ 
║ 13 ║ 2000-01-01 00:00:25 ║ WALMART ║ 1 ║    ║ 
║ 14 ║ 2000-01-01 00:00:25 ║ BESTBUY ║ 0 ║    ║ 
║ 15 ║ 2000-01-01 00:00:25 ║ RADIOSHACK ║ 5 ║    ║ 
║ 16 ║ 2000-01-01 00:00:30 ║ WALMART ║ 1 ║    ║ 
║ 17 ║ 2000-01-01 00:00:30 ║ BESTBUY ║ 6 ║    ║ 
║ 18 ║ 2000-01-01 00:00:30 ║ RADIOSHACK ║ 2 ║    ║ 
║ 19 ║ 2000-01-01 00:00:35 ║ WALMART ║ 6 ║    ║ 
║ 20 ║ 2000-01-01 00:00:35 ║ BESTBUY ║ 2 ║    ║ 
║ 21 ║ 2000-01-01 00:00:35 ║ RADIOSHACK ║ 8 ║    ║ 
║ 22 ║ 2000-01-01 00:00:40 ║ WALMART ║ 10 ║    ║ 
║ 23 ║ 2000-01-01 00:00:40 ║ BESTBUY ║ 2 ║    ║ 
║ 24 ║ 2000-01-01 00:00:40 ║ RADIOSHACK ║ 3 ║    ║ 
║ 25 ║ 2000-01-01 00:00:45 ║ WALMART ║ 6 ║    ║ 
║ 26 ║ 2000-01-01 00:00:45 ║ BESTBUY ║ 4 ║    ║ 
║ 27 ║ 2000-01-01 00:00:45 ║ RADIOSHACK ║ 5 ║    ║ 
║ 28 ║ 2000-01-01 00:00:48 ║ WALMART ║ 1 ║    ║ 
║ 29 ║ 2000-01-01 00:00:48 ║ BESTBUY ║ 0 ║    ║ 
║ 30 ║ 2000-01-01 00:00:48 ║ RADIOSHACK ║ 5 ║    ║ 
║ 31 ║ 2000-01-01 00:00:50 ║ WALMART ║ 6 ║    ║ 
║ 32 ║ 2000-01-01 00:00:50 ║ BESTBUY ║ 4 ║    ║ 
║ 33 ║ 2000-01-01 00:00:50 ║ RADIOSHACK ║ 5 ║    ║ 
║ 34 ║ 2000-01-01 00:00:55 ║ WALMART ║ 1 ║    ║ 
║ 35 ║ 2000-01-01 00:00:55 ║ BESTBUY ║ 0 ║    ║ 
║ 36 ║ 2000-01-01 00:00:55 ║ RADIOSHACK ║ 5 ║    ║ 
║ 37 ║ 2000-01-01 00:01:00 ║ WALMART ║ 1 ║    ║ 
║ 38 ║ 2000-01-01 00:01:00 ║ BESTBUY ║ 0 ║    ║ 
║ 39 ║ 2000-01-01 00:01:00 ║ RADIOSHACK ║ 5 ║    ║ 
╚═════╩═════════════════════╩════════════╩══════╩════════════════╝ 

我的查询是:

UPDATE my_table AS t 
INNER JOIN 
(select ID, 
    (select avg(price) from my_table as t2 
    where 
     t2.datetime between subtime(t1.datetime, '00:14:59') and t1.datetime AND 
     t2.name = t1.name 
    ) as average 
from my_table as t1 
where 
    minute(datetime) in (0,15,30,45)) as sel 
ON t.ID = sel.ID 
SET 15_MIN_AVERAGE = average 

我对柱DATETIME(这是类型DATETIME的)的指标,但我想使用的功能,例如因为where子句中的minute()和subtime()基本上使索引无效。

我的表有大约160万条记录(大约一个记录每5分钟)。目前,运行此查询需要很长时间(超过一个小时),这是不可接受的。

你有什么建议来优化呢?

非常感谢!

+0

嗯,你是对的索引。 MySQL索引[** TIPS **](http://mysql.rjweb.org/doc.php/index_cookbook_mysql) –

回答

0

我认为是更好的创建range表这一点。这里是一个很好的例子

generate days from date range

的表像这样10年*365天* 24小时* 4季度= 350K行。但该指数将工作完美。

所以,你的表应该是这样的:

id start     end 
    1  2016-11-10 10:00:00 2016-11-10 10:04:59 
    2  2016-11-10 10:05:00 2016-11-10 10:09:59 
    3  2016-11-10 10:10:00 2016-11-10 10:14:59 

和您的查询将分配id为每个日期时间

SELECT t.name, r.id, AVG(t.price) 
FROM my_table t 
JOIN range r 
    ON t.`DATETIME` BETWEEN r.start 
         AND r.end 
GROUP BY t.name, r.id 

替代

id start     end 
    1  2016-11-10 10:00:00 2016-11-10 10:05:00 
    2  2016-11-10 10:05:00 2016-11-10 10:10:00 
    3  2016-11-10 10:10:00 2016-11-10 10:15:00 


SELECT t.name, r.id, AVG(t.price) 
FROM my_table t 
JOIN range r 
    ON t.`DATETIME` >= r.start AND t.`DATETIME` < r.end 
GROUP BY t.name, r.id 
+1

这些样本范围从一个到另一个都有一分钟的间隔。一个范围的结束点应该等于下一个范围的开始点,然后**不要在连接使用> =和<中使用BETWEEN **。这样就没有差距或重叠。 –

+0

@Used_By_Already我明白你说了什么。但是我不知道日期会有什么差距或重叠,你能告诉我一个例子吗?我宁愿这个设置,因为允许我使用'BETWEEN' –

+0

请在你的答案中看到替代。在另一种情况下,没有第二个缺口(对不起,我的意思是1秒钟之前),也没有使用“之间”(包括端点> =和<=)之间的“重叠”,例如, ref:http://sqlblog.com/blogs/aaron_bertrand/archive/2011/10/19/what-do-between-and-the-devil-have-in-common.aspx请注意,MySQL现在支持时间单位更小比1秒 –

0

这是一个变种胡安卡洛斯的范围建议Oropeza。我怀疑在自己的表中实际存储15分钟的平均数是有意义的,但在这里我已经按照要求应用了它。但请注意,我无法自己将列称为“datetime”这样的保留字,因此我使用“quantatetime”代替。

还有就是你并不需要超过1000个15分钟的间隔,如果你这样做,那么你需要调整交叉连接等的数量笛卡儿积扩展到更大的东西固有的假设。

另外假设这是仅在需要时被添加新数据时,该逻辑将重新处理为其中存储的平均为空的时间的所有行。

update table1 
inner join (
    select 
      dr.start_date 
     , dr.end_date 
     , avg(t.price) avg_price 
    from table1 t 
    inner join (
      SELECT 
        (x.a + (y.b*10)+(z.c*100))+ 1 n 
       , TRIM(min_date + INTERVAL 15*(x.a + (y.b*10)+(z.c*100)) MINUTE) start_date 
       , TRIM(min_date + INTERVAL 15*(x.a + (y.b*10)+(z.c*100)) MINUTE) + INTERVAL 15 MINUTE end_date 
      FROM (
       select 
         cast(date(min(pricedatetime)) as datetime) min_date 
        , cast(date(max(pricedatetime)) as datetime) max_date 
       from Table1 
       where 15_MIN_AVERAGE IS NULL 
       ) m 
      CROSS JOIN (
        SELECT 0 AS a UNION ALL 
        SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL 
        SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL 
        SELECT 9 
       ) x 
      CROSS JOIN (
        SELECT 0 AS b UNION ALL 
        SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL 
        SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL 
        SELECT 9 
       ) y 
      CROSS JOIN (
        SELECT 0 AS c UNION ALL 
        SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL 
        SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL 
        SELECT 9 
       ) z 
      where TRIM(min_date + INTERVAL 15*((x.a + (y.b*10)+(z.c*100))-1) MINUTE) < max_date 
     ) dr on t.pricedatetime >= dr.start_date and t.pricedatetime < dr.end_date 
    group by 
      dr.start_date 
     , dr.end_date 
    ) g on table1.pricedatetime >= g.start_date and table1.pricedatetime < g.end_date 
set `15_MIN_AVERAGE` = g.avg_price 
; 

请注意,我非常刻意避免使用之间。之间是NOT一个很好的选择日期范围,因为它同时包括更低和更高的边界,并且作为其结果是posisble为行重复计算。相反,只需使用> = <的组合并且该问题完全消失。此外请注意,采用这种方法时,如果定价时间列精确到秒或亚秒级,如果避免在范围之间使用将保持准确,则无关紧要。http://sqlfiddle.com/#!9/299150/1

0

计划A:

可作为一个工作演示时,上述建议升级到MariaDB的10.2和使用“窗口函数”做这样的“移动平均”。

计划B:每15秒回顾表中的15分钟并计算当前3行的所有平均值。将它们存储(通过INSERT,而不是UPDATE)到一个单独的表格中。你永远不需要重新计算它们。通过在datetime上有一个索引,你不需要看超过180行来完成计算。这需要比在计算下一组平均值之前的15秒钟少得多的时间。

新表格上没有id,也没有旧表格。你有一个非常好的“自然”主键(name, datetime)。如果您需要priceaverage,您可以使用原始表格JOIN“汇总表”。

C计划:切换到“指数移动平均”;这是很简单的计算:新的平均

old_average + 0.1 * (new_value - old_average) 

选择一个较小的值(低于0.1),如果你希望均要打圆场;更大的价值使其更快地响应。