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:	
 ALL

possible_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:	
 range

possible_keys:	
 prize_id

	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 key:	
 prize_id

	
 	
 	
 	
 	
 	
 key_len:	
 12

	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 ref:	
 NULL

	
 	
 	
 	
 	
 	
 	
 	
 	
 rows:	
 186246

	
 	
 	
 	
 	
 	
 	
 	
 Extra:	
 Using	
 where;	
 
インデックス追加
■	
 BEFORE

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	
 (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
60xFAST
インデックスが使えない場合	
•  テーブルの後ろから読み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	
 2094599G;



***************************	
 1.	
 row	
 ***************************

	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 id:	
 1

	
 	
 select_type:	
 SIMPLE

	
 	
 	
 	
 	
 	
 	
 	
 table:	
 prize_history

	
 	
 	
 	
 	
 	
 	
 	
 	
 type:	
 range

possible_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版

MySQL勉強会 インデックス編.2013 08-02