web-dev-qa-db-ja.com

数百万行のMySQL平均クエリを最適化

4つのテーブルがあります(それぞれにメイン列を指定しました)。

  1. monitors(ID)
  2. monitor_node(monitor_id、node_id、average_response_time)
  3. monitor_checks(id、monitor_id、node_id、response_time)
  4. nodes(ID)

彼らの関係:

1モニター-Nチェック

Nモニター-Nノード

1つのチェック-1つのモニターと1つのノード


したがって、私は各モニターの多くのチェックをログに記録しています。 Monitor_checksには数百万行(約5億まで)があります。

チェックの別の束(〜1kチェック)を挿入するとき、ノードごとの平均応答時間を計算する必要があります(表monitor_node、列average_response_time)。

私はそれを間違った方法で行っていると確信しており、より高速なソリューションが必要です。私が今していること:monitor_checksに1000行を挿入した後、各モニターの平均応答時間を計算します(monitor_idでグループ化)。次に、この情報に基づいて配列を作成し、一括更新を行うためにinsert ... on duplicate key updateテーブルにmonitor_nodeを使用します。平均応答時間は別として、他のいくつかの属性を計算します。この属性は、この一括更新での応答時間と一緒になります。

Infoで配列を作成し、insert ... on duplicate key updateを実行するのは十分高速です。

遅いクエリは次のとおりです。

select monitor_id, avg(response_time) as avg_response_time 
from `monitor_checks` 
where `node_id` = 2 
group by `monitor_id`

これは、約400万行の場合、約10〜20秒かかります。

また、すべてのチェックに基づいて平均時間を取得する必要がないこともわかりました。最後の50〜100行も使用できます。しかし、私はこれを行う方法を理解できませんでした。各モニターのチェックをグループ化し、それらをいくつかの行数に制限するための素晴らしいソリューションを手に入れました: 各グループの最新の2つのレコードを取得する方法 ですが、時間がかかりすぎました。

したがって、問題は、monitor_nodeテーブルに〜1k行を挿入するときに、monitor_checksの平均応答時間をすばやく再計算する方法ですか。

DBフィドル: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=bd95afc030361bf1d87f8bc5c3935c2f

最終的な望ましい結果:

monitor_id  node_id average_response_time
1   1   0.30
1   2   0.25
2   1   0.55 
2   2   0.65
1
Victor

最新の100応答時間を含む配列をmonitor_nodeに追加します。最近の応答時間を追加する場合は、100を超えるものをドロップします。新しい平均を計算する必要がある場合、追加のデータは必要ありません。

画像は1000語を超える可能性があるため、 DB Fiddle を更新しました。トリガーは仕事をしますが、そのロジックは挿入を行う仕事に組み込むことができます。

create trigger monitor_checks_air
  after insert on monitor_checks for each row
  begin
  update monitor_node mn
    set mn.rec_resp_times =
          json_extract(
             json_array_insert(mn.rec_resp_times,'$[0]',new.response_time),
             '$[0 to 99]'),
        mn.average_response_time = (
            select sum(jt.rt)
              from json_table(mn.rec_resp_times,
                             '$[*]' columns( rt double path '$[0]')) as jt
          )
          / json_length(mn.rec_resp_times) 
    where mn.monitor_id = new.monitor_id
      and mn.node_id    = new.node_id;
  end;

最速のクエリは、実行しないクエリです。

1
Gerard H. Pille

要約表を作成して保守します。それはおそらく次のようなものでしょう。各監視ノードペア間の1時間ごとの平均応答を気にしていると思いますか?

CREATE TABLE response_summary (
    hr DATETIME NOT NULL,  -- a number representing an hourly bucket
    monitor_id ...,
    node_id ...,
    ct INT UNSIGNED NOT NULL,
    sum_resp FLOAT NOT NULL,
    PRIMARY KEY(node_id, monitor_id, hr)
    INDEX(monitor),
    INDEX(hr),
) ENGINE=InnoDB

1Kの読み取り値のバッチがある場合、そのテーブルにそれを注ぐことができる複数の方法があります。 MySQLでほとんどの作業を行うことを想定してコーディングします。 (これの一部は代わりにクライアントで行うことができます。)

CREATE TEMPORARY TABLE batch (hr, monitor_id, node_id, response);
INSERT INTO batch VALUES (,,,), (,,,), (,,,), ..., (,,,);  -- the 1K readings
INSERT INTO response_summary
        (hr, monitor_id, node_id,
         ct, sum_resp)
    ON DUPLICATE KEY UPDATE
        ct = ct + VALUES(ct)
        sum_resp = sum_resp + VALUES(sum_resp)
    SELECT CONCAT(LEFT(dt, 13), ':00:00') AS hr,
           monitor_id, node_id,
           COUNT(*), SUM(response)
        FROM batch
        GROUP BY node_id, monitor_id, hr;

詳細については:

推奨されるPKの列の順序は、where node_id = 2 group by monitor_id

1日の平均を取得するには:

    SUM(sum_resp) / SUM(ct)

これは、あなたが持っている多くのサンプルの平均を見つけるのに役立ちます。たとえば、1日の平均を24平均の平均にする必要がある場合は、何か他のことを行う必要があります。

1
Rick James