そごうソフトウェア研究所

SOA、開発プロセス、ITアーキテクチャなどについて書いています。Twitterやってます@rsogo

EXPLAINによるMySQLの実行計画の確認

担当しているソフトウェアで、運用しているとMySQL内のレコードが増えて性能が悪化するというお問い合わせをいただきました。 そこで、Indexを改善しようということになったのですが、どこにIndexを付けるべきか確認するためにMySQLの実行計画を確認しました。

自分のバックグラウンドとしては、データベースは特別詳しいわけではないので、 Indexは論理的に付けてはいるものの、それがあっているのかどうか確証はありませんでした。

で、調べたところMySQLでも実行計画が確認できるみたいなので、今回、それを使って確認を行ってみました。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 8.8 クエリー実行プランの理解

対象のテーブル

こんなテーブルです。ユーザーのセッション情報を格納するためのテーブルで、ログアウトされないと、レコードが蓄積されていきます。

MySQL [apppot]> desc UserSession;
+-----------------+--------------+------+-----+---------+-------+
| Field           | Type         | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+-------+
| userToken       | varchar(255) | NO   | PRI | NULL    |       |
| appTableId      | bigint(20)   | YES  |     | NULL    |       |
| companyId       | bigint(20)   | YES  |     | NULL    |       |
| deviceUDID      | varchar(255) | YES  |     | NULL    |       |
| loginDate       | datetime     | YES  |     | NULL    |       |
| tokenExpireDate | datetime     | YES  |     | NULL    |       |
| userId          | bigint(20)   | YES  |     | NULL    |       |
+-----------------+--------------+------+-----+---------+-------+
7 rows in set (0.05 sec)

こんな感じのSQLが実行されます。

select * from UserSession 
where userId=205 
and appTableId=4 
and deviceUDID='apppotsdkjs'
 and tokenExpireDate>='2018-01-12 22:16:13';

MySQLの実行計画の確認は手軽で、EXPLAINを頭に付けるだけでOKです。

Index追加前

rowsに122381という値が入っていますが、いわゆるフルスキャンが発生して、全部のレコードをチェックしていることがわかりました。そりゃ遅いですね。

MySQL [apppot]> EXPLAIN select * from UserSession where userId=205 and appTableId=4 and deviceUDID='apppotsdkjs' and tokenExpireDate>='2018-01-12 22:16:13';
+----+-------------+-------------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table       | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-------------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | UserSession | ALL  | NULL          | NULL | NULL    | NULL | 122381 | Using where |
+----+-------------+-------------+------+---------------+------+---------+------+--------+-------------+
1 row in set (0.01 sec)

userIdにIndexを追加してみる

試しにuserIdにIndexを追加してみます。 ログインするユーザーが十分バラけていれば、これでも早くなりそうな感じがします。

ALTER TABLE UserSession 
ADD INDEX index_userId(userId);

実行計画はこんな感じ。チェックする対象のrowsの値が1/4になってますね。

MySQL [apppot]> EXPLAIN select * from UserSession where userId=205 and appTableId=4 and deviceUDID='apppotsdkjs' and tokenExpireDate>='2018-01-12 22:16:13';
+----+-------------+-------------+------+---------------+--------------+---------+-------+-------+-------------+
| id | select_type | table       | type | possible_keys | key          | key_len | ref   | rows  | Extra       |
+----+-------------+-------------+------+---------------+--------------+---------+-------+-------+-------------+
|  1 | SIMPLE      | UserSession | ref  | index_userId  | index_userId | 9       | const | 33436 | Using where |
+----+-------------+-------------+------+---------------+--------------+---------+-------+-------+-------------+
1 row in set (0.00 sec)

複数カラムのIndex

MySQLは複数のIndexあっても、どれか1つしか使ってくれません。 そこで、複数のカラムで1つのIndexを作ることができます。遅いSQLの条件で使っているカラムの組み合わせてIndexを作ります。

ALTER TABLE UserSession 
ADD INDEX index_get_session(userId, appTableId, deviceUDID, tokenExpireDate);

実行計画を見てみましょう。rowsの値が3桁減ってますね。 possible_keysが使用可能なIndex、keyが実際に使われたIndexということのようです。 新しく付けたindex_get_sessionが使われてますね。

MySQL [apppot]> EXPLAIN select * from UserSession where userId=205 and appTableId=4 and deviceUDID='apppotsdkjs' and tokenExpireDate>='2018-01-12 22:16:13';
+----+-------------+-------------+-------+--------------------------------+-------------------+---------+------+------+-----------------------+
| id | select_type | table       | type  | possible_keys                  | key               | key_len | ref  | rows | Extra                 |
+----+-------------+-------------+-------+--------------------------------+-------------------+---------+------+------+-----------------------+
|  1 | SIMPLE      | UserSession | range | index_userId,index_get_session | index_get_session | 792     | NULL |  258 | Using index condition |
+----+-------------+-------------+-------+--------------------------------+-------------------+---------+------+------+-----------------------+
1 row in set (0.08 sec)

以上です。