2016-11-25 9 views
-1

MySQLクエリを最適化しようとしています。特定の店舗で15分ごとにアイテムの価格の移動平均をテーブルの列に更新しようとしています。MySQLクエリの最適化 - インデックスの使用

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にインデックスを持っているが、私はこのような機能を使用して思う:

私のテーブルには、私のクエリがある

╔═════╦═════════════════════╦════════════╦══════╦════════════════╗ 
║ 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 ║    ║ 
╚═════╩═════════════════════╩════════════╩══════╩════════════════╝ 

次のような構造を持っていますwhere句のminute()およびsubtime()は基本的にインデックスを無効にするためです。

私のテーブルには約160万レコード(5分ごとに約1レコード)があります。現在、(1時間以上)このクエリを実行するには非常に時間がかかりますが、これは容認できません。

最適化するにはどうすればよいですか?

ありがとうございました!

+0

あなたはインデックスに関して適切です。 MySQLインデックス[** TIPS **](http://mysql.rjweb.org/doc.php/index_cookbook_mysql) –

答えて

0

これはrangeテーブルを作成する方が良いと思います。ここ10年間、このようなテーブル* 365日* 24時間* 4四半期= 350K行

generate days from date range

良い例です。しかし、インデックスは完璧に動作します。

だからあなたの表はすべきのようになります。

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

これらのサンプル範囲は、すべて1分の間隔を空けています。 1つの範囲の終点は、次の範囲の開始点と同じでなければなりません。次に、** =と<を使用して結合で** BETWEEN **を使用しないでください。この方法では、ギャップや重複はありません。 –

+0

@Used_By_あなたの言うことをすでに理解しています。しかし、私は日時がギャップやオーバーラップになるのを見ない、あなたは私に例を示すことができますか?私はむしろ私が 'BETWEEN'を使用できるようにするので、この設定は –

+0

あなたの答えに代替を見てください。別の方法では、1つ目の2番目のギャップはありません(1秒前のことを申し訳ありません)。また、 "between"(両端のエンドポイント> =と<=)の間に " ref:http://sqlblog.com/blogs/aaron_bertrand/archive/2011/10/19/what-do-between-and-the-devil-have-in-common.aspx MySQLは時間単位をより小さくサポートすることに注意してください1秒以上 –

0

これは変異体であり、フアン・カルロスの範囲提案のオロペザ実際に15分の平均値を独自のテーブルに格納するのは理にかなっていると思われますが、ここでは要求どおりに適用しました。しかし、私は "datetime"のような予約語を列と呼び、代わりに "pricingatetime"を使用することはできません。

1000個の15分間隔を必要としないという固有の前提があります。その場合、デカルト積を拡大してより大きなものに拡張する必要があります。

また、新しいデータが追加された場合にのみ必要であると仮定すると、ロジックは、格納された平均値がNULLの日付のすべての行を再処理します。

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 
; 

私は非常に意図的に使用することを避けてください。間にはではないは、下限と上限の両方の境界を含むため、日付範囲に適しています。その結果、行がダブルカウントされる可能性があります。代わりに、単に=> <の組み合わせを使用すると、その問題は完全に消えます。また、この方法では、priceatetime列が2番目または1秒後に正確であるかどうかは関係ありません。作業のデモとして利用できる提案上記

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より)小さい値を選択しています。それをより速く応答させるために大きな値。