bigmac-jp blog

web開発関連のメモ

MySQL メモ11 複合index

複合indexのメモ。よくありがちな開始日付と終了日付を持つテーブルを範囲検索する場合に、indexを貼る。

下記SQLでテーブルを作成。

CREATE TABLE schedule (
  id int(11) NOT NULL,
  start_date datetime NOT NULL,
  end_date datetime NOT NULL,
  PRIMARY KEY (id)
)

とりあえず60件のデータを登録。

id	start_date	end_date
1	2014/01/01	2014/04/01
2	2014/02/09	2014/05/09
3	2014/03/20	2014/06/16
4	2014/04/28	2014/07/24
5	2014/06/06	2014/08/31
6	2014/07/15	2014/10/08
7	2014/08/23	2014/11/15
8	2014/10/01	2014/12/23
9	2014/11/09	2015/01/30
10	2014/12/18	2015/03/09
11	2015/01/26	2015/04/16
12	2015/03/06	2015/05/24
13	2015/04/14	2015/07/01
14	2015/05/23	2015/08/08
15	2015/07/01	2015/09/15
16	2015/08/09	2015/10/23
17	2015/09/17	2015/11/30
18	2015/10/26	2016/01/07
19	2015/12/04	2016/02/14
20	2016/01/12	2016/03/23
21	2016/02/20	2016/04/30
22	2016/03/30	2016/06/07
23	2016/05/08	2016/07/15
24	2016/06/16	2016/08/22
25	2016/07/25	2016/09/29
26	2016/09/02	2016/11/06
27	2016/10/11	2016/12/14
28	2016/11/19	2017/01/21
29	2016/12/28	2017/02/28
30	2017/02/05	2017/04/07
31	2017/03/16	2017/05/15
32	2017/04/24	2017/06/22
33	2017/06/02	2017/07/30
34	2017/07/11	2017/09/06
35	2017/08/19	2017/10/14
36	2017/09/27	2017/11/21
37	2017/11/05	2017/12/29
38	2017/12/14	2018/02/05
39	2018/01/22	2018/03/15
40	2018/03/02	2018/04/22
41	2018/04/10	2018/05/30
42	2018/05/19	2018/07/07
43	2018/06/27	2018/08/14
44	2018/08/05	2018/09/21
45	2018/09/13	2018/10/29
46	2018/10/22	2018/12/06
47	2018/11/30	2019/01/13
48	2019/01/08	2019/02/20
49	2019/02/16	2019/03/30
50	2019/03/27	2019/05/07
51	2019/05/05	2019/06/14
52	2019/06/13	2019/07/22
53	2019/07/22	2019/08/29
54	2019/08/30	2019/10/06
55	2019/10/08	2019/11/13
56	2019/11/16	2019/12/21
57	2019/12/25	2020/01/28
58	2020/02/02	2020/03/06
59	2020/03/12	2020/04/13
60	2020/04/20	2020/05/21

indexを貼る前に範囲検索を実行。

mysql> explain select * from schedule where start_date >= '2018/3/1' AND end_date <= '2018/10/1';
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | schedule | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   60 |    11.11 | Using where |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

typeカラムが"ALL"になっているため、フルテーブルスキャンになっていることがわかる。

start_dateとend_dateの複合indexを貼る。

mysql> ALTER TABLE schedule ADD INDEX index_start_end(start_date, end_date);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

複合indexを貼った後に再度範囲検索の実行計画を実行。

mysql> explain select * from schedule where start_date >= '2018/3/1' AND end_date <= '2018/10/1';
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
| id | select_type | table    | partitions | type  | possible_keys   | key             | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | schedule | NULL       | range | index_start_end | index_start_end | 5       | NULL |   21 |    33.33 | Using where; Using index |
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

typeカラムが"range"になっているため、インデックスを用いた範囲検索になっていることがわかる。


webシステムの一覧画面で検索条件に"開始日付フォーム"と"終了日付フォーム"があって、入力されている場合検索条件に追加して、一覧の絞り込むを行う。
みたいな場合を想定。

①開始日付と終了日付が入力されている場合。 ※冒頭のSQLと同じパターン
開始日付:2018/12/1
終了日付:2019/4/1

mysql> explain select * from schedule where start_date >= '2018/12/1' AND end_date <= '2019/4/1';
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
| id | select_type | table    | partitions | type  | possible_keys   | key             | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | schedule | NULL       | range | index_start_end | index_start_end | 5       | NULL |   13 |    33.33 | Using where; Using index |
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

②開始日のみ入力されている場合。
開始日付:2018/12/1
終了日付:null

mysql> explain select * from schedule where start_date >= '2018/12/1';
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
| id | select_type | table    | partitions | type  | possible_keys   | key             | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | schedule | NULL       | range | index_start_end | index_start_end | 5       | NULL |   13 |   100.00 | Using where; Using index |
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

この場合もtypeカラムは"range"になっているので、indexが使われいる。


③終了日のみ入力されている場合。
開始日付:null
終了日付:2018/12/1

mysql> explain select * from schedule where end_date <= '2018/12/1';
+----+-------------+----------+------------+-------+---------------+-----------------+---------+------+------+----------+--------------------------+
| id | select_type | table    | partitions | type  | possible_keys | key             | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+----------+------------+-------+---------------+-----------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | schedule | NULL       | index | NULL          | index_start_end | 10      | NULL |   60 |    33.33 | Using where; Using index |
+----+-------------+----------+------------+-------+---------------+-----------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

typeカラムが"index"になっているため、フルインデックススキャンとなる。
複合indexの注意点としてindexの定義時のカラムの順番が重要となる。
今回の複合index(index_start_end )は、"start_date"、"end_date"の順番に定義した。
③のSQLの場合、条件に"start_date"がないため、複合index(index_start_end )が使われないことになった。


複合indexのカラム順番を逆にした複合indexを貼る。

mysql> ALTER TABLE schedule ADD INDEX index_end_start(end_date, start_date);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

再度③のSQLを実行。

mysql> explain select * from schedule where end_date <= '2018/12/1';
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
| id | select_type | table    | partitions | type  | possible_keys   | key             | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | schedule | NULL       | range | index_end_start | index_end_start | 5       | NULL |   45 |   100.00 | Using where; Using index |
+----+-------------+----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

この場合もtypeカラムは"range"になっているので、追加した複合index(index_end_start )が使われいる。