web-dev-qa-db-ja.com

すべてが異なる検索クエリで使用される可能性がある場合、どの列にインデックスを付ける必要がありますか?

バックグラウンド

現在、4つの異なる都市にある映画シアターチェーンのウェブサイトに取り組んでいます(将来的に拡張される可能性があります)。彼らはすべての都市に同じ単一データベースのWebサイトを使用しています。つまり、特定のテーブルに、各行が属する都市のIDを保持する列が必要です。

現在、3つの異なるテーブルがあります。

  • Cinemas-各都市の映画館(IDと名前)が含まれています。
  • Movies-映画館で上映された、または上映されるすべての映画が含まれます。
  • Showtimes-すべての都市のすべての映画の上映時間が含まれています。

Showtimesテーブルの構造は次のとおりです。

Column Name   | Column Type  | Description
--------------+--------------+---------------
ID            | BIGINT       | (Primary) Unique ID for each showtime (perhaps unnecessary?)
CinemaID      | TINYINT      | Foreign key bound to Cinemas.ID
MovieID       | BIGINT       | Foreign key bound to Movies.ID
Showtime      | DATETIME     | At what date and time the movie will show 

(will contain multiple rows for each movie, i.e. one row for each showtime)

このテーブルの使用方法

ウェブサイトのユーザーは次のことができる必要があります。

  • 選択した都市の現在/今後のすべての映画と上映時間(日付順)を表示します。

    クエリの例(バックエンド):

    SELECT MovieID, Showtime FROM Showtimes WHERE CinemaID = ? ORDER BY Showtime
    
  • 1つの映画を選択し、その特定のタイトルのみ(選択した都市で)の上映時間をすべて表示します。

    クエリの例:

    SELECT Showtime FROM Showtimes WHERE CinemaID = ? AND MovieID = ? ORDER BY Showtime
    
  • 1日を選択し、その日のみのすべての映画と上映時間を表示します(選択した都市で)。

    クエリの例:

    SELECT MovieID, Showtime FROM Showtimes WHERE CinemaID = ? AND (Showtime BETWEEN [date 12:00 AM] AND [date 12:00 PM])
    

そのため、当然、列のインデックスを作成する必要があると判断しました。

問題

私が問題を抱えているのは、列に適切にインデックスを付ける方法を決定/決定することです。列ごとに1つのインデックスは非常に高価に見える[1][2] そのため、複合インデックスを検討し始めました。これは正しい選択のようですが、さらに混乱を招きました。

(私が読んだものに基づいて)私の理解から、選択性の順序で列をインデックスに追加し、最も選択性の高いものにする必要があります(私はそれが最も一意的/最もカーディナリティが高いことを意味すると思いますか?)列複合インデックスの最初[3] (私の場合はShowtime列になります)。これに関する唯一の問題は、最初の列が検索クエリに含まれている場合にのみ、データベースでインデックスを使用できることです。[4][5]、これは現在、どちらのクエリにも含まれていません。

質問

すべての使用シナリオをカバーするために、どのような種類のインデックスを列に適用する必要がありますか? (最後のシナリオは省略できますが、最初の2つは必須です)

一部の列ではすべての列に複合インデックスを使用する必要がありますか、それとも各列に個別のインデックスが必要ですか?

この表は、新しい上映時間を追加するために、週に最大数回更新されます。

脚注

1MySQLインデックス-ベストプラクティスは何ですか?

2テーブルのすべての列にインデックスを付ける

インデックスの列の順序はどのくらい重要ですか?(質問)

4インデックスの列の順序はどのくらい重要ですか?(#2上位投票の回答)

5いつ複合インデックスを使用する必要がありますか?

5
Visual Vincent

複合主キー

主キーを(CinemaID, MovieID, Showtime)の複合キーとして定義します。

これらの3つの列は各行を一意に識別するため、個別のID列を用意する必要はありません。

コンポジット(セカンダリ)インデックス

このPKでは、クエリに必要な追加のインデックスは(CinemaID, Showtime)のみです。

なぜこれらのインデックスなのか?

インデックスが使用されていることを考える良い方法は、それらをスプレッドシートの列の順序と考えることです。

(CinemaID, MovieID, Showtime)のスプレッドシートが各列で連続してソートされていると想像してください。

すべてのクエリにCinemaIDが存在するため、スプレッドシートのCinemaIDの「セクション」をすばやく見つけることができます。次に、MovieIDで検索するクエリの場合、2列目で「サブセクション」を簡単に見つけることができます。ここで、MovieIDは検索された値と一致します。

Showtimeの3列目もソートされているので、その映画館でその映画のすべての上映時間を見つけるのがどれほど速くて簡単かを想像できます。 DBMSは同様の方法で処理を行い、それらの結果を非常に迅速に取得できます。

他のクエリと同様に、それらはすべてCinemaIDで始まり、次にShowtimeを何らかの方法で使用します。結果にはMovieIDも必要です。

したがって、(CinemaID, Showtime)インデックスで対応できます。繰り返しになりますが、CinemaIDはスプレッドシートの「セクション」を簡単に見つけ(類推で)、すべての可能な上映時間(および複数の画面があると仮定すると、重複する可能性があります)が順番にリストされます。そして、それらの値によって簡単に検索および/またはソートされます。

さらに良いことに、主キーにはMovieIDが含まれているため、その列は定義された列の後のすべてのセカンダリインデックスに含まれます(少なくともMySQL InnoDB-他のエンジンも含まれますが、必ずしもすべてではありません)。

セカンダリインデックスの「スプレッドシート」の3列目だと考えてください。列が存在する理由は、必要に応じて、メインテーブル(別名、クラスター化インデックス、InnoDB)へのルックアップを実行するために使用できる主キーのすべての部分を持つためです。この単純なケースでは、ルックアップは必要ありません。そのため、そのダブルルックアップを必要としないため、さらに効率的です。

この主キーと単一のセカンダリインデックスだけを使用すると、リストしたクエリのいずれでも優れたパフォーマンスが得られます。

結果論

複数の画面に同時に上映される映画がある場合、これが各行を一意に識別するという私の仮定は正しくない可能性があります。これらの画面を個別に識別できるようにしたい場合、私の解決策は最適ではありません(その状況に対して別の解決策を提供できます。私に知らせてください。)

5
Willem Renzema
_WHERE CinemaID = ? ORDER BY Showtime  -- and
WHERE CinemaID = ? AND (Showtime BETWEEN [date 12:00 AM] AND [date 12:00 PM])  -- need:
INDEX(CinemaID, Showtime)

WHERE CinemaID = ? AND MovieID = ? ORDER BY Showtime  -- needs:
INDEX(CinemaID, MovieId, Showtime)  -- or
INDEX(MovieId, CinemaID, Showtime)
_

トリプル(MovieId、CinemaID、Showtime)が一意であると仮定すると、idを削除して、

_PRIMARY KEY(CinemaID, MovieId, Showtime)
INDEX(CinemaID, Showtime)
_

WHEREの主要部分が_MovieID=..._であるケースはありますか?

映画館-各都市とその映画館(IDと名前)のリスト:

_SELECT Cinema, CinemaID FROM Cimemas;  -- (no index needed)
_

映画-映画館で上映された、または上映される映画のリスト。

_SELECT DISTINCT MovieID FROM ShowTimes WHERE CinemaID=...
INDEX(CinemaID, MovieID)  -- already handled by my proposed PK
_

上映時間-すべての都市のすべての映画のすべての上映時間のリスト。 -これは巨大な出力です。要件を再検討してください。つまり、クライアントがそれで何をするかを考えます。

これらのインデックスのほとんどは、 indexing cookbook および composite indexingの調査から推定できます

SELECTsが表示されるまで、インデックス(単一列とコンポジット、および詳細)を提案しなかったことに注意してください。

「選択性の順序でインデックスへの列を作成し、選択性を最も高くします(これは、最も一意であるか、/カーディナリティが最も高いことを意味していると思いますか?)」-いいえ。選択性はではありません複合インデックスを設計するためのキー。すべての_=_列から、anyの順序で開始します。 (私のクックブックはこのトピックについて詳しく述べています。)

「インデックスは、最初の列が検索クエリに含まれている場合にのみテーブルで使用できます」-ほとんどの場合、真です。 2つのインデックスを推奨する方法に注意してください(PKはインデックスです)。 WHEREを無視して、インデックスを_GROUP BY_または_ORDER BY_に使用できる場合があります。しかし、それらはまれです。

「複合インデックスの最初...ショータイム」-複合インデックスの最初にDATETIMEを置くことは通常逆効果です。特に、3番目のクエリではcanboth列のINDEX(CinemaID, Showtime)を使用できますが、INDEX(Showtime, CinemaID)の両方列ではありません。これは簡単にわかります。映画館と上映時間の2つのリストを書き出すことを考えてください。シネマで最初に1つのリストを並べ替えます(la INDEX(cinema, time);もう1つは時間で並べ替えます。特定のシネマのすべての行を、時間範囲全体でまとめて(「クラスター化」)します。

同じ映画を同時に表示している2つの画面が原因でScreenIDもある場合は、それをPKに貼り付けます。ただし、すべてのインデックスを再考する必要があります。

(申し訳ありません、ウィレム、私はあなたの答えを読む前に私の答えを書きました-私たちはほとんど同じことを言います)

5
Rick James

あなたは非常に小さなテーブルでサイズ*を最適化しています。これは基本的にブリッジテーブルであるため、主キーは実際には必要ありません。必要な場合でも、bigintである必要はなく、映画IDもbigintである必要はありません。数十億本以上の映画が必要だと思われる場合は、署名なしで使用しますが、すぐに映画の数の制限に遭遇することはないと思います。

主な用語、映画、映画、上映時間のそれぞれにインデックスを付けます。パフォーマンスを確認し、許容できない場合はインデックスの組み合わせを追加します。

インデックスなしで試してから(サロゲートキーとして使用されるauto_increment列のpkを除く)、妥当な量のデータ、たとえば200映画館X(週に5本の新しい映画x毎日4本の番組の上映時間x 7) x(520週間aka 10年))= 14,560,000レコード。

率直に言って、このサイズであれば、テーブル全体のスキャンが悪くないはずです。

サイズについては、完全にカバーするインデックスは13バイトで、16に切り上げられるため、16 x 14,560,000/1024は227,500k別名227mb別名.277です。テーブルと可能性のある各カバリングインデックスの時間は4で、テーブルとインデックスは最大1.25ギガです。すべての組み合わせインデックスは同じサイズになることに注意してください。データに対する重みが異なるだけです...

これを本当に高速化する方法は、履歴データを別のテーブルに置くか、パーティションを使用することです。その場合、次の月のデータがあれば、5 x 4 x 7 x 30 x 200 = 120,000レコードなので、120kレコードのスキャンは問題になりません。その時点で、すべてをメモリに保持し、ローカル辞書を使用して検索することができます。

また、現在画面が1つしかないため、1日の映画あたり4〜6のレコードしか記録せず、映画館の数も少ないため、クレイジーなように拡大すると想定しています。 1か月あたり1万件のレコード。毎日手動で更新されるExcelスプレッドシートに値を保存すると、これで十分なパフォーマンスが得られる可能性は十分にあります。

インデックスの選択性のためのPS。最も正確なものではなく、最も広い範囲が必要です。単一のレコードを探すのではなく、グループを探すのです。つまり、グループを返す列にインデックスを付ける必要があります。特に、これは、映画を日ごとに検索する場合、日付と時刻を2つの列に分割して、日付にインデックスを付けることができることを意味します(おそらく1つの時刻に1つ)。

*スペースを最適化しているとおっしゃったのは、インデックスのコストが気になるからです。インデックスのコストには、挿入/削除/更新が遅いものとディスク容量の2つの形式があります。基本的に各インデックスのコストがテーブルと同じくらいであることを意味する完全なカバーインデックスの場合。 16バイト(または24)のテーブルに単一の行を追加する時間は短く、累積されません(つまり、行数に関係なく基本的に一定です)。ディスク領域の蓄積はゆっくりですが、増加します。

1
jmoreno

最初に私は間違いなくしないでください(CinemaID, MovieID, Showtime)の複合キーとして主キーを定義します。それが何を意味するかを考えてみてください。エントリをリンクしたい場合は、IDだけでなく、常にそれらのデータが必要です。

データベースを作成するときは、 正規化の規則 に固執することをお勧めします。彼らは理由のために作られました。できるだけ高速でメモリを節約したいと考えていますが、ボトルネックになるかどうかはまだわかりません。完全に正規化するのと同じようにスキームを作成します。次に、テストデータ(数百万のエントリ)を入力して、パフォーマンスに深刻な影響がないか確認します。これを行うと、「このクエリは他のクエリの2倍のメモリを消費する」などの数値も表示され、これを確認できます。

現在、これは 時期尚早の最適化 のにおいがします。

ソフトウェアでのパフォーマンスは食べ物の味のようなものです。それが良いかどうかは推測できますが、結局、とにかく試す必要があります。

0
Emarci