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

Preview:

Citation preview

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版

Recommended