MySQLの実行計画（EXPLAIN）に関する覚え書

# はじめに

MySQLはバージョン5.6を利用します。
「access_logs」というテーブルに「access_logs_idx1」「access_logs_idx2」という２つのインデックスが定義されているものとします。


# 実行計画を確認する（EXPLAINコマンド）

まずは以下のようなSELECT文について...

```
mysql> SELECT count(*) FROM access_logs where year = 2015;
+----------+
| count(*) |
+----------+
|    14219 |
+----------+
1 row in set (0.02 sec)
```

実行計画を見てみる。

```
mysql> EXPLAIN SELECT count(*) FROM access_logs where year = 2015;
+----+-------------+-------------+------+-----------------------------------+------------------+---------+-------+------+--------------------------+
| id | select_type | table       | type | possible_keys                     | key              | key_len | ref   | rows | Extra                    |
+----+-------------+-------------+------+-----------------------------------+------------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | access_logs | ref  | access_logs_idx1,access_logs_idx2 | access_logs_idx1 | 2       | const | 8025 | Using where; Using index |
+----+-------------+-------------+------+-----------------------------------+------------------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
```

MySQLではSQLの先頭に「EXPLAIN」をつけることで、実行計画を確認することができる。

- possible_keys : MySQL がこのテーブル内の行の検索に使用するために選択できるインデックス
- key : MySQL が実際に使用することを決定したキー (インデックス) 
- rows : MySQL がクエリーを実行するために調査する必要があると考える行数


# インデックスを指定する（USE INDEXコマンド）

特定のインデックスを利用させたい場合は、「USE INDEX」でインデックスを指定する。
※除外したいインデックスがある場合は「IGNORE INDEX」で指定する。

```
mysql> EXPLAIN SELECT count(*) FROM access_logs USE INDEX(access_logs_idx2) where year = 2015;
+----+-------------+-------------+------+------------------+------------------+---------+-------+------+--------------------------+
| id | select_type | table       | type | possible_keys    | key              | key_len | ref   | rows | Extra                    |
+----+-------------+-------------+------+------------------+------------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | access_logs | ref  | access_logs_idx2 | access_logs_idx2 | 2       | const | 8025 | Using where; Using index |
+----+-------------+-------------+------+------------------+------------------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
```

```
mysql> SELECT count(*) FROM access_logs USE INDEX(access_logs_idx2) where year = 2015;
+----------+
| count(*) |
+----------+
|    14219 |
+----------+
1 row in set (0.00 sec)
```

オプティマイザが選択したインデックスより、明示的に指定したインデックスの方が処理時間が明らかに早い場合は、統計情報が間違っている（古い）可能性が高い。
※複数回実行してみて比較すること。


# クエリが実際に読み込んだ件数を確認する

まずはセッションスコープのステータス変数をクリアする。

```
mysql> FLUSH STATUS;
Query OK, 0 rows affected (0.00 sec)
```

そしてチェックしたいSQL文を実行。

```
mysql> SELECT count(*) FROM access_logs where year = 2015;
+----------+
| count(*) |
+----------+
|    14219 |
+----------+
1 row in set (0.00 sec)
```

セッションスコープのステータス変数を確認する。

```
mysql> SHOW SESSION STATUS LIKE 'Handler\_%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_commit             | 1     |
| Handler_delete             | 0     |
| Handler_discover           | 0     |
| Handler_prepare            | 0     |
| Handler_read_first         | 0     |
| Handler_read_key           | 1     |
| Handler_read_last          | 0     |
| Handler_read_next          | 14219 |
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 0     |
| Handler_read_rnd_next      | 0     |
| Handler_rollback           | 0     |
| Handler_savepoint          | 0     |
| Handler_savepoint_rollback | 0     |
| Handler_update             | 0     |
| Handler_write              | 0     |
+----------------------------+-------+
16 rows in set (0.01 sec)
```

「14219」が実際に読み込んだ件数。


# 統計情報の再作成

統計情報の再作成してみる。

```
mysql> ANALYZE TABLE access_logs;
+------------------------+---------+----------+----------+
| Table                  | Op      | Msg_type | Msg_text |
+------------------------+---------+----------+----------+
| opu_pf2_db.access_logs | analyze | status   | OK       |
+------------------------+---------+----------+----------+
1 row in set (0.00 sec)
```

再作成後に再度実行計画を確認する。

```
mysql> EXPLAIN SELECT count(*) FROM access_logs where year = 2015;
+----+-------------+-------------+------+-----------------------------------+------------------+---------+-------+------+--------------------------+
| id | select_type | table       | type | possible_keys                     | key              | key_len | ref   | rows | Extra                    |
+----+-------------+-------------+------+-----------------------------------+------------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | access_logs | ref  | access_logs_idx1,access_logs_idx2 | access_logs_idx2 | 2       | const | 7904 | Using where; Using index |
+----+-------------+-------------+------+-----------------------------------+------------------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
```

`rows` が `8025` から `7904` に変わった。
今回は `key` は変わらなかったが、統計情報が古いままで実行計画を確認しても実際のSQLと異なる場合がある（場合によっては選択されるインデックスも異なる可能性もある）ので、実行計画を確認するときは、統計情報を最新化してからやった方がいいってことかな。

# 参考

- [MySQL 5.6 リファレンスマニュアル>EXPLAIN 出力フォーマット](https://dev.mysql.com/doc/refman/5.6/ja/explain-output.html)
- [SQL実行計画の疑問解決には「とりあえずEXPLAIN」しよう](https://thinkit.co.jp/article/9658)