【MySQL】インデックスまとめ
背景
業務や個人で開発を行う際、インデックスについて悩むタイミングが度々あるため、備忘録を残そうと思いました。
本記事で扱うDBはMySQL(ver8.0)になります。
インデックスの基本
インデックスとは
MySQLのインデックスは、データベースの検索パフォーマンスを向上させるためのデータ構造です。インデックスを設定することで、データの検索やソートが効率化され、大量のデータを扱う際でも高速にクエリを実行できるようになります。
インデックスを利用することで、データベースは全件スキャン(Full Table Scan)を避け、インデックスを辿ることで効率的に目的のデータを見つけることができます。
インデックスのメリット
検索速度の向上
インデックスがある場合、検索処理が迅速になります。
ソートの最適化
ORDER BY や GROUP BY を伴うクエリの実行速度が向上します。
ユニーク性の保証
PRIMARY KEY や UNIQUE インデックスを使用することで、データの一意性を強制できます。
インデックスのデメリット
ただ、インデックスはバンバン貼っていけば良いわけではなく、下記のようなデメリットもあります。
書き込み速度の低下
INSERT、UPDATE、DELETE 操作時にインデックスも更新されるため、処理が遅くなることがあります。
よって、頻繁に更新されるテーブルにはあまり貼るべきではありませんね。
ストレージ消費
インデックスは追加のストレージを消費します。特に複数のインデックスを設定する場合、ストレージ使用量が増加します。
誤用によるパフォーマンスの低下
不適切なインデックス設定や過剰なインデックスは、逆にパフォーマンスを低下させる場合があります。
インデックスの数は最小限に抑えるべきです。
インデックスの種類
PRIMARY KEY
テーブル内の各行を一意に識別するインデックスで、主キー制約とともに使用されます。
自動的にユニークインデックスが作成されます。
UNIQUE
一意性が必要な列に設定します。同じ値を持つ複数の行が存在しないことを保証します。
INDEX(またはKEY)
通常のインデックスで、クエリの検索を高速化するために設定します。一意性の保証はありません。
インデックスの貼り方
インデックスの確認
SHOW INDEX FROM users;
インデックスの追加(テーブル変更)
ALTER TABLE users
ADD INDEX idx_name (name); -- name列にインデックスを追加
インデックスの追加
CREATE INDEX idx_email
ON users (email); -- email列にインデックスを追加
複合インデックスの追加
CREATE INDEX idx_email_created_at
ON users (email, created_at); -- 複合インデックスを追加
ソート順を指定するインデックスの追加(未指定の場合はASCになる)
CREATE INDEX idx_name_created_at_desc
ON users (name ASC, created_at DESC);
インデックスの削除
DROP INDEX idx_name ON users;
インデックスの確認方法(EXPLAIN)
EXPLAINコマンドを使用することで、クエリがどのように実行されるか(実行計画)、インデックスが正しく使用されているかを確認できます。
EXPLAINの使い方
EXPLAIN をクエリの先頭に付けて実行します。
実行すると、クエリ実行に関する情報を含む結果が返されます。(下記は例です。)
EXPLAIN SELECT * FROM users WHERE name = 'Hoge' ORDER BY created_at DESC;
id | select_type | table | type | possible_keys | key | key_len | rows | extra |
---|---|---|---|---|---|---|---|---|
1 | SIMPLE | users | ref | idx_name | idx_name_created_at | 256 | 10 | Using where; Using index |
EXPLAINの出力項目
項目数が多いので、確認する際よく見る箇所に絞って説明します。
type
テーブルアクセスの種類を示します。効率の良い順に並べると以下のようになります。
-
system:
システムテーブルへのアクセス(最も効率的)。 -
const:
一致するデータが1行だけの場合。 -
eq_ref:
主キーまたはユニークインデックスを使った結合。 -
ref:
非ユニークインデックスを使用。 -
range:
範囲検索。 -
index:
インデックススキャン。 -
ALL:
フルテーブルスキャン(最も効率が悪い)。
ALL
が表示された場合効率が悪い状態のため、インデックスの追加を検討する必要がある、という感じになります。
possible_keys
クエリに利用可能なインデックスの候補を示します。
ここがNULL
の場合、クエリに適したインデックスがないということになります。
key
実際に使用されたインデックスを示します。
インデックスが使用されていない場合はNULL
が表示されます。
rows
クエリでスキャンされる行数の推定値です。
この値が大きい場合、クエリのパフォーマンスに悪い影響を与える可能性があります。
extra
実行に関する追加情報を提供します。
下記のうち、Using indexが表示されればOK、temporaryやfilesortが表示されたら改善する必要あり、といった感じです。
-
Using index:
インデックスだけでデータを取得(カバリングインデックス)。 -
Using where:
WHERE条件でフィルタリングが行われた。 -
Using temporary:
一時テーブルが使用された(最適化が必要)。 -
Using filesort:
ファイルベースのソートが発生(インデックスでソート可能なら改善の余地あり)。
私の場合、基本的にはtypeを見てALLではないことを確認し、ALLならrowsとextraを見て改善策を考える流れで確認しています。
インデックス貼ったのにpossible_keysに表示されない…ってこともよくありますね😅
インデックスが効かないパターン
先頭列を使用しない複合インデックス
複合インデックスの先頭列が条件に含まれない場合、インデックスが無効化されます。
インデックスが (name, created_at) の順で定義されている場合:
SELECT * FROM users WHERE created_at > '2024-01-01';
そのため、複合インデックスでは定義する順番を意識する必要があります。
ワイルドカードの先頭使用(部分一致・後方一致検索)
LIKE検索でワイルドカード(%)を文字列の先頭に置くと、インデックスが無効化されます。
SELECT * FROM users WHERE name LIKE '%Hoge';
基本的に検索は前方一致、またはフルテキストで検索するのが吉です。
計算や関数の使用
クエリ内でインデックス列に計算や関数を使用すると、インデックスが無効化されます。
SELECT * FROM users WHERE YEAR(created_at) = 2023;
インデックスは列の元の値に基づいて構築されるため、関数や計算を使用するとインデックスを活用できなくなります。
ORDER BYの順序とインデックスが合致しない
下記のように、name
は昇順、created_at
は降順でインデックスを設定した場合。
CREATE INDEX idx_example ON users (name, created_at);
インデックス作成時にソート順を指定しなかった場合、暗黙的に昇順で設定されます。
よって、下記と同等になります。
CREATE INDEX idx_example ON users (name ASC, created_at ASC);
よってorder by
で下記のように指定(ソート順が一致しない)した場合、インデックスは効きません。
SELECT *
FROM users
WHERE name = 'Hoge'
ORDER BY created_at DESC;
降順でソートしたい場合には、ソート順をDESCで指定した専用のインデックスを設ける必要があります。
CREATE INDEX idx_example ON users (name ASC, created_at DESC);
インデックスの設計について
これは私見ですが、インデックスはDB設計の段階で設定すべきかなと思っています。(当たり前かもしれませんが…。)
DB設計時に検索に使われるカラムを設定すると思います。その際、データ量や検索方法を鑑みて一緒にインデックスを貼り、100万件くらいテストデータを投入してEXPLAINで実行計画を確認…というところまでがDB設計なのかなと思っています。
過去に参画した案件では、パフォーマンスが良くなかったりslow queryが出たクエリに対し、後付けでインデックスを設定していました。
ただ、このやり方だとどうしても場当たり的になってしまい、insert時の負荷なども考えるとなかなか最適なインデックスを設定するのは難しいなと感じる場面が多かったです。
よって、最近はDB設計の段階でインデックスを設計・設定するようになりました。以降、リリース後にパフォーマンスの問題が出るケースも少なくなり、やはりインデックスを考慮した上でDB設計すべきなんだなーというのが所感です。
Discussion