优化MySQL查询 - 使用索引

时间:2016-11-25 03:44:34

标签: mysql optimization

我正在尝试优化MySQL查询。我正在尝试使用特定商店每15分钟移动平均一件商品的价格更新表格列。

我的表格具有以下结构

╔═════╦═════════════════════╦════════════╦══════╦════════════════╗
║ 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分钟大约有一条记录)。目前,运行此查询(超过一小时)需要很长时间,这是不可接受的。

您建议如何优化它?

非常感谢!

3 个答案:

答案 0 :(得分: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 :(得分:0)

这是Juan Carlos Oropeza提出的范围提案的变体。我怀疑实际上只将15分钟的平均值存储在自己的表中是有道理的,但在这里我已按要求应用它。但请注意,我不能将自己称为“datetime”这样的保留字,因此我使用了“pricingatetime”。

有一个固有的假设,你不需要超过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 之间是日期范围的一个好选项,因为它包括较低和较高的边界,因此可以对行进行双重计算。而只需使用&gt; =与&lt;的组合。那个问题完全消失了。另请注意,使用此方法,如果您在范围之间使用时间段精确到秒或亚秒,则无关紧要。

上述建议可在http://sqlfiddle.com/#!9/299150/1

作为工作演示版提供

答案 2 :(得分:0)

计划A:升级到MariaDB 10.2并使用“窗口函数”来做这样的“移动平均线”。

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

新表上没有id,旧表也没有。你有一个非常好的'自然'主键(name, datetime)。如果您同时需要JOINprice,则可以average原始表中的“摘要表”。

计划C:切换到“指数移动平均线”;计算起来要简单得多:新的平均值是

old_average + 0.1 * (new_value - old_average)

如果您希望平均值更平滑,请选择较小的值(小于0.1);一个更大的值,使其响应更快。