私のシステムは、イベントの追加のみのログを保存する必要があります。現在、すべての関連データを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'
結果のクエリは高速で、フルテーブルスキャンを必要としませんか?もしそうなら、これはより良い設定のようです。
INDEX(result, event_type, event_timestamp)
は、INDEX(result)
およびINDEX(result, event_type)
の必要性を取り除きます。
盲目的に(255)
を使用すると、インデックスとクエリが損傷します。現実的な限界に戻ります。
提案した方法でテーブルを分割しても、何も効果がなく、ほとんどのクエリに悪影響を及ぼします。特に、複数のインデックスを効果的に使用することも、「複合」インデックスを使用することもできません(複数のテーブルが含まれるため)。一方、「ログ」が非常に大きくなると、そのような「正規化」によってディスクのフットプリントが大幅に縮小されます。これ自体、パフォーマンスにプラスの影響を与えます。
timestamp
などの「連続」値を正規化しない。これにより、「範囲」値にインデックスを付けることが非現実的になるため、パフォーマンスが大幅に低下します。
「正しい」テーブルをオプションにする必要がない限り、LEFT
を使用しないでください。 LEFT
は、「左」のテーブルを最初にスキャンする必要があることを意味する場合があります。あなたの例では、それはevents
のフルスキャンにつながります。
LEFT JOIN
をJOIN
に変更すると(最後の例)、オプティマイザーはテーブルの中から選択して、最初のテーブルを決定します。これは、関連する列の単一列インデックスの元のケースと同等です(ただし遅い)。
「カーディナリティが低い」列(status
およびresult
)は、それ自体でインデックスを付けるのに実質的に役に立ちません。 「コンポジット」インデックスの最初の列が有効である場合があります。
ほとんどのテーブルには、現実的に適用される限られた数のクエリがあります。テーブルが多くの異なるクエリを要求すると言っている場合、私のアドバイスは次のとおりです。
=
でテストされた(WHERE
で)列であることを確認してください。 「範囲」もある場合(timestamp
を使用した例のように)、最後に配置します。 (複数の範囲テストをパントします。)ANDing
のWHERE
の順序は重要ではありませんが、INDEX
の列の順序は重要です。MySQLは「ビットマップ」インデックスを実装していません。努力する価値はほとんどありません。 MySQLは、複合インデックスをシミュレートする不器用な方法である「_ANDs
の」「インデックスマージ交差」を実装します。 「ORs
」の「インデックスマージユニオン」(OR
用)は時々便利です。しかしUNION
は同じくらい良いと思われます。
あなたは「データウェアハウス」アプリケーションのようです。そのための最高のスピードアップは、ビルドして維持することです 要約テーブル 。たとえば、result
とevent_type
で分類された1日のカウントの概要は、クエリがはるかに小さく、はるかに高速になります。 (10倍のスピードアップはかなり可能です。)さらに、Summaryテーブルに異なるインデックスを設定すると、現在持っているログジャムをいくらか壊すことができます。 (小計をSUM
合計するとCOUNT
になります。)
いいえ、提案した方法でテーブルを複数のテーブルに分割しても、状況によっては役に立ちません。 するを助けるいくつかのヒントはここにあります:
InnoDBでは、テーブルのストレージ全体が主キーを中心に編成されていることに加えて、セカンダリインデックスを使用することが重要です。さらに、セカンダリインデックスは、ターゲットキーを主キーで参照します。比較的ランダムな主キーが大きいと、テーブルのBツリーのバランスが崩れ、二次インデックスが不必要に大きくなる可能性があります。したがって、パフォーマンスを向上させるために最初にできることは、適切なサイズの整数である自動生成された主キーを持つようにイベントテーブルを変更することです。
次に、インデックスは、スキャンする行数を大幅に減らす場合にのみ役立ちます。可能性のある値の数がテーブルの行数に比べて少ないインデックスを意味する「低カーディナリティ」インデックスを持っていると、ほとんど役に立ちませんが、時間とスペースがかかるため無駄です。維持する。おそらく、event_type
、status
、またはresult
のインデックスを単独で使用してもメリットは得られません。
同様に、インデックスは、列が完全一致として使用されている場合、または列が自然な順序であり、範囲一致で使用されている場合にのみ役立ちます。結合インデックスには、最も具体的でないものから最も具体的なものの順に列があり、列が最初の列から始まる順番にが完全一致のターゲットである範囲でのみ使用されます(ただし最後の列は範囲にすることができます)。
すべてのクエリにevent_timestamp
またはlogged_at
のいずれかの時間範囲が含まれることを期待しているため、実際にはこれらの列のみ(主キーを除く)にインデックスを付ける必要があります、他のインデックスはこれらと組み合わせて機能しないためです。ただし、1列または2列との完全一致で結果を一般的にフィルター処理する場合は、それらの列を最初に、タイムスタンプを最後に配置する結合インデックスを使用するのが理にかなっています。もちろん、インデックスでは正しいタイムスタンプを使用し、タイムスタンプを1つだけ使用する必要があります。 1つのインデックスでevent_timestamp
とlogged_at
を組み合わせると、役に立たないよりも悪い結果になります。
また、テーブルがばかげて大きくならないようにするために、ある種のデータウェアハウス戦略を構築するのにも役立ちます。 1か月または1年前に発生したイベントに必要なクエリの種類と、それらがどれだけの頻度で必要かを考えてから、定期的にデータを別の場所に移動します(おそらく要約します)。
あなたはすでに答えを知っています、
1つの解決策は、次のようなインデックスを作成することです。
CREATE INDEX successful_silly_events ONイベント(result、event_type、event_timestamp);
早くやれよ。
@Rick Jamesに同意します。 VARCHAR
は特にPKでクエリの速度が低下している可能性が高いです。
event_id
および他の*_id
列は、タイプINT(または類似)である必要があります。
このアプローチの欠点は、インデックスの作成に時間がかかり、このクエリの速度が上がるだけであることです。このテーブルに異なる列を含む別のクエリを作成すると、正方形のクエリに戻ります。
私はライブテーブルでテーブルロックを回避します。
events_new
event_id
events_new
to int実際、私はこれを、PKのVARCHAR
タイプ(group_id、person_id、client_id)を持つ他の関連テーブルに対しても行います。