web-dev-qa-db-ja.com

「イベント」テーブルを再設計する必要がありますか

私のシステムは、イベントの追加のみのログを保存する必要があります。現在、すべての関連データを1つのテーブルに格納するデータベーステーブルがあります。

CREATE TABLE `events` (
        `event_id` VARCHAR(255) NOT NULL PRIMARY KEY,
        `event_type` VARCHAR(255) NOT NULL,
        `event_timestamp` DATETIME,
        `group_id` VARCHAR(255),
        `person_id` VARCHAR(255),
        `client_id` VARCHAR(255),
        `name` VARCHAR(768),
        `result` VARCHAR(255),
        `status` VARCHAR(255),
        `logged_at` DATETIME,
        `severity` VARCHAR(255),
        `message` LONGTEXT,
        INDEX `event_type_index` (`event_type`),
        INDEX `event_timestamp_index` (`event_timestamp`),
        INDEX `group_id_index` (`group_id`),
        INDEX `person_id_index` (`person_id`),
        INDEX `client_id_index` (`client_id`),
        INDEX `name_index` (`name`),
        INDEX `result_index` (`result`),
        INDEX `status_index` (`status`),
        INDEX `logged_at_index` (`logged_at`),
      ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8_general_ci

しかし、WHERE句に複数の属性を持つクエリがまだ遅いことに気づきました。例えば:

SELECT
  count(e.event_id) as total
FROM events e
WHERE
  e.result='Success' AND
  e.event_type='some_silly_event' AND
  e.event_timestamp > '2019-01-01 00:00:00'

1つの解決策は、次のようなインデックスを作成することです。

CREATE INDEX successful_silly_events
ON events (result,event_type,event_timestamp); 

このアプローチの欠点は、インデックスの作成に時間がかかり、このクエリの速度が上がるだけであることです。このテーブルに異なる列を含む別のクエリを作成すると、正方形のクエリに戻ります。

最初からイベントテーブルを複数のテーブルに分割するほうがよいでしょうか?例えば:

CREATE TABLE `events` (
        `event_id` VARCHAR(255) NOT NULL,
        `logged_at` DATETIME,
        `severity` VARCHAR(255),
        `message` LONGTEXT,
        PRIMARY KEY (event_id),
        INDEX `logged_at_index` (`logged_at`),
      ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8_general_ci

CREATE TABLE `event_types` (
        `event_id` VARCHAR(255) NOT NULL,
        `event_type` VARCHAR(255) NOT NULL,
        PRIMARY KEY event_id REFERENCES events(event_id)
        INDEX `event_type_index` (`event_type`),
      ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8_general_ci

CREATE TABLE `event_timestamps` (
        `event_id` VARCHAR(255) NOT NULL,
        `event_timestamp` VARCHAR(255) NOT NULL,
        PRIMARY KEY event_id REFERENCES events(event_id)
        INDEX `event_timestamp_index` (`event_timestamp`),
      ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8_general_ci

CREATE TABLE `event_groups` (
        `event_id` VARCHAR(255) NOT NULL,
        `group_id` VARCHAR(255) NOT NULL,
        PRIMARY KEY event_id REFERENCES events(event_id)
        INDEX `group_id_index` (`group_id`),
      ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8_general_ci

他のすべてのイベント属性についても同様で、通常はイベントテーブルにインデックスを付けます。このようにして、同様のクエリを作成できます。

SELECT
  count(e.event_id) as total
FROM events e
  LEFT JOIN event_results er ON e.event_id=er.event_id
  LEFT JOIN event_types ety ON e.event_id=et.event_id
  LEFT JOIN event_timestamps eti ON e.event_id=et.event_id 
WHERE
  er.result='Success' AND
  ety.event_type='some_silly_event' AND
  eti.event_timestamp > '2019-01-01 00:00:00'

結果のクエリは高速で、フルテーブルスキャンを必要としませんか?もしそうなら、これはより良い設定のようです。

2
Dave

INDEX(result, event_type, event_timestamp)は、INDEX(result)およびINDEX(result, event_type)の必要性を取り除きます。

盲目的に(255)を使用すると、インデックスとクエリが損傷します。現実的な限界に戻ります。

提案した方法でテーブルを分割しても、何も効果がなく、ほとんどのクエリに悪影響を及ぼします。特に、複数のインデックスを効果的に使用することも、「複合」インデックスを使用することもできません(複数のテーブルが含まれるため)。一方、「ログ」が非常に大きくなると、そのような「正規化」によってディスクのフットプリントが大幅に縮小されます。これ自体、パフォーマンスにプラスの影響を与えます。

timestampなどの「連続」値を正規化しない。これにより、「範囲」値にインデックスを付けることが非現実的になるため、パフォーマンスが大幅に低下します。

「正しい」テーブルをオプションにする必要がない限り、LEFTを使用しないでください。 LEFTは、「左」のテーブルを最初にスキャンする必要があることを意味する場合があります。あなたの例では、それはeventsのフルスキャンにつながります。

LEFT JOINJOINに変更すると(最後の例)、オプティマイザーはテーブルの中から選択して、最初のテーブルを決定します。これは、関連する列の単一列インデックスの元のケースと同等です(ただし遅い)。

「カーディナリティが低い」列(statusおよびresult)は、それ自体でインデックスを付けるのに実質的に役に立ちません。 「コンポジット」インデックスの最初の列が有効である場合があります。

ほとんどのテーブルには、現実的に適用される限られた数のクエリがあります。テーブルが多くの異なるクエリを要求すると言っている場合、私のアドバイスは次のとおりです。

  • ユーザーが求めるクエリを監視します。列のtypicalの組み合わせを追跡します。
  • いくつかの2および3列のインデックスを実装します。
  • インデックスのfirst列は、=でテストされた(WHEREで)列であることを確認してください。 「範囲」もある場合(timestampを使用した例のように)、最後に配置します。 (複数の範囲テストをパントします。)
  • ANDingWHEREの順序は重要ではありませんが、INDEXの列の順序は重要です。
  • 最適なインデックスの作成の詳細: http://mysql.rjweb.org/doc.php/index_cookbook_mysql

MySQLは「ビットマップ」インデックスを実装していません。努力する価値はほとんどありません。 MySQLは、複合インデックスをシミュレートする不器用な方法である「_ANDsの」「インデックスマージ交差」を実装します。 「ORs」の「インデックスマージユニオン」(OR用)は時々便利です。しかしUNIONは同じくらい良いと思われます。

あなたは「データウェアハウス」アプリケーションのようです。そのための最高のスピードアップは、ビルドして維持することです 要約テーブル 。たとえば、resultevent_typeで分類された1日のカウントの概要は、クエリがはるかに小さく、はるかに高速になります。 (10倍のスピードアップはかなり可能です。)さらに、Summaryテーブルに異なるインデックスを設定すると、現在持っているログジャムをいくらか壊すことができます。 (小計をSUM合計するとCOUNTになります。)

5
Rick James

いいえ、提案した方法でテーブルを複数のテーブルに分割しても、状況によっては役に立ちません。 するを助けるいくつかのヒントはここにあります:

InnoDBでは、テーブルのストレージ全体が主キーを中心に編成されていることに加えて、セカンダリインデックスを使用することが重要です。さらに、セカンダリインデックスは、ターゲットキーを主キーで参照します。比較的ランダムな主キーが大きいと、テーブルのBツリーのバランスが崩れ、二次インデックスが不必要に大きくなる可能性があります。したがって、パフォーマンスを向上させるために最初にできることは、適切なサイズの整数である自動生成された主キーを持つようにイベントテーブルを変更することです。

次に、インデックスは、スキャンする行数を大幅に減らす場合にのみ役立ちます。可能性のある値の数がテーブルの行数に比べて少ないインデックスを意味する「低カーディナリティ」インデックスを持っていると、ほとんど役に立ちませんが、時間とスペースがかかるため無駄です。維持する。おそらく、event_typestatus、またはresultのインデックスを単独で使用してもメリットは得られません。

同様に、インデックスは、列が完全一致として使用されている場合、または列が自然な順序であり、範囲一致で使用されている場合にのみ役立ちます。結合インデックスには、最も具体的でないものから最も具体的なものの順に列があり、列が最初の列から始まる順番にが完全一致のターゲットである範囲でのみ使用されます(ただし最後の列は範囲にすることができます)。

すべてのクエリにevent_timestampまたはlogged_atのいずれかの時間範囲が含まれることを期待しているため、実際にはこれらの列のみ(主キーを除く)にインデックスを付ける必要があります、他のインデックスはこれらと組み合わせて機能しないためです。ただし、1列または2列との完全一致で結果を一般的にフィルター処理する場合は、それらの列を最初に、タイムスタンプを最後に配置する結合インデックスを使用するのが理にかなっています。もちろん、インデックスでは正しいタイムスタンプを使用し、タイムスタンプを1つだけ使用する必要があります。 1つのインデックスでevent_timestamplogged_atを組み合わせると、役に立たないよりも悪い結果になります。

また、テーブルがばかげて大きくならないようにするために、ある種のデータウェアハウス戦略を構築するのにも役立ちます。 1か月または1年前に発生したイベントに必要なクエリの種類と、それらがどれだけの頻度で必要かを考えてから、定期的にデータを別の場所に移動します(おそらく要約します)。

2
Old Pro

あなたはすでに答えを知っています、

1つの解決策は、次のようなインデックスを作成することです。

CREATE INDEX successful_silly_events ONイベント(result、event_type、event_timestamp);

早くやれよ。

0
Wilson Hauck

@Rick Jamesに同意します。 VARCHARは特にPKでクエリの速度が低下している可能性が高いです。

event_idおよび他の*_id列は、タイプINT(または類似)である必要があります。

このアプローチの欠点は、インデックスの作成に時間がかかり、このクエリの速度が上がるだけであることです。このテーブルに異なる列を含む別のクエリを作成すると、正方形のクエリに戻ります。

私はライブテーブルでテーブルロックを回避します。

  • 新しいテーブルを作成します:events_new
  • 変化する event_idevents_new to int
  • イベントデータをevents_newテーブルにコピーする
  • イベントの名前をevents_bakに変更(超高速)
  • Events_newをeventsに名前変更(超高速)

実際、私はこれを、PKのVARCHARタイプ(group_id、person_id、client_id)を持つ他の関連テーブルに対しても行います。

0
denormalizer