2016-11-25 99 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),如果你希望均要打圓場;更大的價值使其更快地響應。