VillageSQL is a drop-in replacement for MySQL with extensions.
All examples on this page work on VillageSQL. Install Now →
メンタルモデル
800 ページの本を考えてみてください。「TCP/IP」の記述をすべて見つけるには、すべてのページを読むこともできます。これがフルテーブルスキャンです。あるいは巻末の索引を調べ、「TCP/IP — 42、107、391 ページ」を見つけて、そのページに直接向かうこともできます。データベースのインデックスがしているのはこれです。 MySQL のデフォルトのインデックス構造は B-tree(平衡木)です。値をソートされた状態で保持するため、高速な検索(二分探索)、範囲スキャン(BETWEEN、>、<)、およびソート済みの出力(ORDER BY)が可能になります。いずれもテーブル全体を読むことなく行えます。
MySQL がインデックスを使う仕組み
クエリを実行すると、MySQL のオプティマイザは、インデックスによって、読む必要のある行を絞り込めるかどうかを確認します。クエリがインデックス付きの列でフィルタリングまたはソートする場合に、インデックスを使います。EXPLAIN: MySQL が実際に何をしているかを見る
EXPLAIN は、MySQL がクエリをどう実行する予定かを示します。key 列は使われたインデックス(なければ NULL)を示し、rows は調べる必要があった行数の見積もりを示します。
type: ref は、MySQL がインデックス検索を使ったことを意味します。type: ALL はフルテーブルスキャンを意味し、通常は避けたいものです。
複合インデックス: 列の順序が重要
複合インデックスは複数の列をカバーします。列の順序が、インデックスをいつ使えるかを決めます。WHERE user_id = 42— 使えます。先頭の列ですWHERE user_id = 42 AND status = 'shipped'— 使えます。両方の列ですWHERE status = 'shipped'— 使えません。先頭の列を飛ばしています
(user_id, status) と (status, user_id) は、用途の異なる別のインデックスです。複合インデックスは、最もよく使うクエリパターンに合わせて設計してください。
カバリングインデックス: インデックスだけでクエリに応える
カバリングインデックスは、クエリが必要とするすべての列を含みます。MySQL は実際の行データを読まずに、インデックスだけでクエリに完全に応えられます。大きなテーブルでは大幅な高速化になります。EXPLAIN は Extra: Using index を示します。行の検索は不要です。
インデックスを追加すべきでない場合
インデックスは多ければ良いというものではありません。すべてのインデックスにはコストがかかります。- 書き込みのオーバーヘッド — すべての INSERT、UPDATE、および DELETE が、テーブル上のすべてのインデックスを更新しなければなりません
- ストレージ — 書き込みの多いデータでは、インデックスがテーブル自体と同じ大きさになることがあります
- オプティマイザの複雑さ — インデックスが多すぎると、オプティマイザが誤ったものを選ぶことがあります
- 列のカーディナリティが高い(異なる値が多い) —
user_id、email、order_id - クエリがテーブルのごく一部の行に絞り込む
- カーディナリティが低い — 3 つの値しか取らない
status、真偽値のis_active - テーブルが小さく、どのみちフルスキャンが速い
- 列への書き込みが多く、クエリされることがまれ
主キーは常にインデックス
InnoDB のテーブルは主キーを中心に構成されています。主キーは別のインデックスではなく、行の物理的な格納順序そのものです。これは次のことを意味します。- 主キーによる検索は、可能な限り最速のクエリです
- 主キーの値が連続していれば、行は物理的に隣接します
- ランダムな主キー(UUID v4 など)は、挿入時に断片化を引き起こします。MySQL の UUIDを参照してください
よくあるインデックスの間違い
インデックス付きの列に関数を使う — 列を関数で包むと、MySQL はインデックスを使えません。% で始まると、インデックスは役に立ちません。
OR 条件を処理するために複数の単一列インデックスをマージできることがありますが、これは当てになりません。大きなテーブルでは、UNION のほうが予測しやすい動作をします。
不足しているインデックスを見つける
スロークエリログは、時間のしきい値を超えたクエリを記録します。何にインデックスを作るべきかを見つける良い出発点です。EXPLAIN を実行します。type: ALL と、数百万単位の rows を探してください。
まとめ
- B-tree インデックスは、高速な検索と範囲スキャンのために値をソートされた状態で保持します
- クエリが先頭のインデックス付き列でフィルタリングまたはソートするとき、MySQL はインデックスを使います
- 複合インデックスの列の順序は重要です。最もよく使うクエリに合わせて設計してください
- カバリングインデックスは最速です。クエリはインデックスだけで完全に応えられます
- インデックスは書き込みにコストがかかります。読み取りの利点がそれに見合う場所にだけ追加してください
- 列を関数で包むと、そのインデックスは無効になります。代わりに範囲条件を使ってください
関連項目
- MySQL の EXPLAIN の読み方 — インデックスが使われていることを検証し、MySQL がそれを選ぶ理由を理解する
- MySQL のカバリングインデックス — 行の検索をまったく行わずにクエリに応えるインデックス
- MySQL のスロークエリログ — どのクエリに先にインデックスが必要かを見つける
- MySQL の INFORMATION_SCHEMA — クエリでインデックスのメタデータと未使用のインデックスを見つける
- MySQL のクエリが遅いのはなぜ? — インデックスが属する、より広いチューニングのワークフロー
- MySQL のページネーション: キーセットと OFFSET の比較 — 大きなテーブルをインデックス主導でページネーションする方法