MySQL社内講習 インデックス編
CROOZ Team Venus
目次
• インデックスとは • EXPLAINとは • インデックスが使えない場合 • インデックスの種類 • インデックスの制限事項 • 複合インデックス • カバリングインデックス • まとめ • 参考資料
『インデックスとは』
インデックスとは 目次のことです
目次が在るから目的のページが探せる
目次がないと….。
最初から全部 読まなければならない
目次は万能じゃない
• ページがしょっちゅう増えるとその都度目次も作り直す
• 目次のページ数自体が大きすぎると本末転倒
• 適正に目次を作成して使用するのが大事
事例に基づいて 考えて見ましょう
アイテムの付与ミスったー
取り消しバッチを作れー!
付与したtimestampを 条件に論理削除すれば
良いよね
付与したtimestampを 条件に論理削除すれば
良いよね 死亡フラグ
demo
対象テーブル
・贈り物テーブル ・ヒストリーデータ ・サンプルのデータは180万件
mysql> UPDATE prize_history SET del_flg = 1 WHERE prize_id = 14 AND ctime BETWEEN '2013-05-18 00:00:00' AND '2013-05-24 00:00:00';
実行するSQL
イベント報酬ID
イベント開始日時
イベント終了日時
どうすれば良かったのか?
まずはEXPLAINを実行
EXPLAINとは
EXPLAINとは
• SQLの実行計画を見るクエリー
• インデックスの使用状況を確認できる
• 実行するSQLの先頭にEXPLAINをつける (※ DELETE文/UPDATE文の場合はSELECT文に置き換える) (※ WHERE句があるSQLはインデックスが使われる)
mysql> EXPLAIN SELECT * FROM prize_history WHERE prize_id = 14 AND ctime BETWEEN '2013-05-18 00:00:00' AND '2013-05-24 00:00:00'\G;*************************** 1. row *************************** id: 1 select_type: SIMPLE table: prize_history type: ALLpossible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 2233733 Extra: Using where
【クエリーのタイプ】:全件検索
【使用されているインデックス】: インデックスが使われていない
【検索行数】:テーブル全行数
【追加情報(処理方法)】:Using temoraryと、 Using filesortは要注意、それぞれ、一時書き出し、ファイルソートが発生していてクエリーが遅くなる傾向がある
SHOW INDEX FROM prize_history;+----------------------+------------+----------+--------------+-----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |+----------------------+------------+----------+--------------+-----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| prize_history | 0 | PRIMARY | 1 | prize_history_id | A | 2235476 | NULL | NULL | | BTREE | | || prize_history | 1 | user_id | 1 | user_id | A | 2235476 | NULL | NULL | | BTREE | | || prize_history | 1 | user_id | 2 | receive_flg | A | 2235476 | NULL | NULL | | BTREE | | |
| prize_history | 1 | user_id | 3 | prize_id | A | 2235476 | NULL | NULL | | BTREE | | |+----------------------+------------+----------+--------------+-----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set (5.73 sec)
インデックスを確認する
ctimeのインデックスがない
インデックス名 インデックス種別 対象カラム
プライマリキー プライマリキー prize_history user_id インデックス user_id,receive_flg,prize_id
ALTER TABLE prize_history ADD KEY prize_id (prize_id,ctime);EXPLAIN SELECT * FROM prize_history WHERE prize_id = 14 AND ctime BETWEEN '2013-05-18 00:00:00' AND '2013-05-24 00:00:00'\G;*************************** 1. row *************************** id: 1 select_type: SIMPLE table: prize_history type: rangepossible_keys: prize_id key: prize_id key_len: 12 ref: NULL rows: 186246 Extra: Using where;
インデックス追加
■ BEFOREmysql> SELECT SQL_NO_CACHE count(*) FROM prize_history WHERE prize_id = 14 AND ctime BETWEEN '2013-05-18 00:00:00' AND '2013-05-24 00:00:00';+----------+| count(*) |+----------+| 86399 |+----------+1 row in set (3.62 sec)
■ AFTER
mysql> SELECT SQL_NO_CACHE count(*) FROM prize_history WHERE prize_id = 14 AND ctime BETWEEN '2013-05-18 00:00:00' AND '2013-05-24 00:00:00';+----------+| count(*) |+----------+| 86399 |+----------+1 row in set (0.06 sec)
BEFORE/AFTER
60x FAST
インデックスが使えない場合
• テーブルの後ろから読みLIMITで制限をかける。ヒストリー系のテーブルで有効
• 使えるインデックスに変換する
ctimeを プライマリキーに変換
イベント期間
2013/04/23
2013/05/18 2013/05/24 2013/05/30
× 最初からフルスキャンするにはデータが多すぎる
2181000
1
2008199 2094599
ctimeをプライマリキーに
変換 データの終わりから フルスキャン指定期間に達したらクエリ終了
対象となる期間の プライマリキーのIDを検索
SELECT prize_history_id FROM prize_history WHERE ctime < '2013-05-18 00:00:00' ORDER BY prize_history_id DESC LIMIT 1;+-----------------------+| prize_history_id |+-----------------------+| 2008199 |+-----------------------+
イベント開催日時 ※バックアップサーバーで実行
SELECT prize_history_id FROM prize_history WHERE ctime < '2013-05-24 00:00:00' ORDER BY prize_history_id DESC LIMIT 1;+-----------------------+| prize_history_id |+-----------------------+| 2094599 |+-----------------------+
イベント終了日時 ※バックアップサーバーで実行
mysql> EXPLAIN SELECT * FROM prize_history WHERE prize_id = 14 AND prize_history_id BETWEEN 2008199 AND 2094599\G;*************************** 1. row *************************** id: 1 select_type: SIMPLE table: prize_history type: rangepossible_keys: PRIMARY key: PRIMARY key_len: 8 ref: NULL rows: 174524 Extra: Using where
期間をプライマリキーに 置き換えてEXPLAIN
UPDATE prize_history SET del_flg=1 WHERE prize_id = 14 AND prize_history_id BETWEEN 2008199 AND 2094599;
UPDATE文を作成
×
1つのSQLでまとめて update/insert/delete しない
詳しくは 次回『リプリケーション』編で
1行更新する UPDATE文を作成する
• プログラムでselectした結果からforで回して、一行更新するのupdate文を実行するバッチを作る
• SELECTした結果をEXCELに貼ってマクロでUPDATE文を作成する
• SQLで1行更新するupdate文のSQLを作成
SQLで1行更新する update文のSQLを作成
SELECT CONCAT( 'UPDATE prize_history ', 'SET del_flg = 1 ', 'WHERE prize_history_id =', prize_history_id,';' ) FROM prize_history WHERE prize_id = 14 AND prize_history_id BETWEEN 2008199 AND 2094599;
UPDATE文作成 ※バックアップサーバーで実行
バックアップ作成
SELECT CONCAT( 'UPDATE prize_history ', 'SET del_flg = ',del_flg,' ', 'WHERE prize_history_id =', prize_history_id,';' ) FROM prize_history WHERE prize_id = 14 AND prize_history_id BETWEEN 2008199 AND 2094599;
※バックアップサーバーで実行
『インデックスの種類』
インデックスの種類
• プライマリキー • ユニークキー • インデックス
インデックスの制限事項
• !=、<>はインデックスが使用できない
• LIKE検索では前方一致のみ使用できる。
『複合インデックスとは』
複数のカラムに対する インデックス
複合インデックスとは
複合インデックスは 先頭から順に部分インデックス
として使用できる。
つまり
• index(user_id,category_id,del_flg) という複合インデックスがあった場合 ×index(user_id,category_id) ×index(user_id) のインデックスは作る必要がない
テスト用テーブル
・サンプルのデータは10万件
カラム名 型
id unsinged int(11)
A unsinged int(11)
B varchar(255) C unsinged int(11)
インデックス名
インデックス種別
対象カラム
pkey プライマリキー Id
index_A インデックス A
index_B インデックス B
index_C インデックス C
index_A_B_C
インデックス A,B,C
複合インデックス が使える場合
• SELECT * FROM test WHERE A=1 and B=2 and C=3
• SELECT * FROM test WHERE A=1 and B=2
• SELECT * FROM test WHERE A=1 ※ INDEX index_a_b_c(A,B,C)の場合
複合インデックス が使えない場合
• SELECT * FROM test WHERE B=2 and C=3
• SELECT * FROM test WHERE A=1 and C=3
• SELECT * FROM test WHERE B=2
※ INDEX index_a_b_c(A,B,C)の場合
複合インデックスは 順番が重要
カバリング インデックスとは
対象となるすべての 検索条件・検索項目を含んだ
複合インデックス
カバリングインデックスの例
• SELECT A,B,C FROM test WHERE A=1 and B=2 and C=3
• SELECT A FROM test WHERE A=1 and B=2
• SELECT A,B,C FROM test WHERE A=1
※ INDEX index_a_b_c(A,B,C)の場合
カバリングインデックス EXPLAIN
EXPLAIN SELECT A,B,C FROM test WHERE A=757 AND b='0.ZYc2FHB0kpo' \G; *************************** 1. row *************************** id: 1 select_type: SIMPLE table: test type: ref possible_keys: index_A,index_B,index_A_B_C key: index_A_B_C key_len: 771 ref: const,const rows: 1 Extra: Using where; Using index
Indexを使っているという意味ではない。Index内のデータを使用しているという意味(カバリングインデックス)
インデックススキャンだけで 完結しているので
非常に高速
通常の場合
インデックス
データベース
クエリ
結果セット
インデックスを元にデータベース
を参照
カバリングインデックスの場合
インデックス
クエリ
結果セット
InnoDBの場合、インデックスにデータが含まれる為、対象データがインデックスに存在すれば、データベース参照なしで結果セットを返す。
だだし、サマリーテーブルを 作るようなものなので、 ディスク容量に注意
まとめ
• ムダなインデックスは作らない。
• インデックスは出来るだけ1つの複合インデックスで複数カバー出来るように作る。カラムの順番が重要。
次回予告
インデックスが効かないとどうなるか
MySQL社内講習 リプリケーション編
『参考資料』 ・ソーシャルゲーム開発者なら知っておきたい MySQL INDEX + EXPLAIN入門 ・MySQL5からのインデックス結合で1テーブル複数インデックスを使う ・実践ハイパフォーマンスMySQL 第2版