Index Hintの使いどき

  • 13
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

通知が多すぎて担当日を見逃してしまい、大遅刻。ごめんなさい!

Index Hintとは

MySQLにはIndex Hint機能が実装されています。
ドキュメントから引用すると、こんな具合の構文ですが、要は使うINDEXを指定することができます。

tbl_name [[AS] alias] [index_hint_list]

index_hint_list:
    index_hint [, index_hint] ...

index_hint:
    USE {INDEX|KEY}
      [FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
  | IGNORE {INDEX|KEY}
      [FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
  | FORCE {INDEX|KEY}
      [FOR {JOIN|ORDER BY|GROUP BY}] (index_list)

index_list:
    index_name [, index_name] ...

たとえば、こんなテーブルがあったとして、

CREATE TABLE friend (
    user_id         INTEGER UNSIGNED NOT NULL,
    friend_user_id  INTEGER UNSIGNED NOT NULL,
    accepted_fg     BOOL NOT NULL DEFAULT 0,
    order_num       INTEGER UNSIGNED NOT NULL,
    PRIMARY KEY (user_id, friend_user_id),
    UNIQUE INDEX friend_order_uniq (user_id, order_num),
    INDEX friendship_idx (accepted_fg, user_id, friend_user_id)
) ENGINE=InnoDB;

こんなデータが入っているとします。

INSERT INTO friend (user_id, friend_user_id, accepted_fg, order_num) VALUES (1, 2, 1, 1), (2, 1, 1, 1);
INSERT INTO friend (user_id, friend_user_id, accepted_fg, order_num) VALUES (1, 3, 1, 2), (3, 1, 1, 1);
INSERT INTO friend (user_id, friend_user_id, accepted_fg, order_num) VALUES (1, 4, 1, 3), (4, 1, 1, 1);
INSERT INTO friend (user_id, friend_user_id, accepted_fg, order_num) VALUES (2, 3, 1, 2), (3, 2, 1, 2);
INSERT INTO friend (user_id, friend_user_id, accepted_fg, order_num) VALUES (4, 2, 1, 2), (2, 4, 1, 3);
INSERT INTO friend (user_id, friend_user_id, accepted_fg, order_num) VALUES (4, 3, 1, 3), (3, 4, 1, 3);
INSERT INTO friend (user_id, friend_user_id, accepted_fg, order_num) VALUES (1, 5, 0, 4), (5, 1, 0, 1);
INSERT INTO friend (user_id, friend_user_id, accepted_fg, order_num) VALUES (1, 6, 0, 5), (6, 1, 0, 1);

そこにこんなSQLを打ちます。

SELECT friend_user_id FROM friend WHERE user_id = 1 AND accepted_fg = 1 ORDER BY order_num LIMIT 5;

実行計画をみてみましょう。(注: これはMySQL5.5での結果です)

mysql> EXPLAIN SELECT friend_user_id FROM friend WHERE user_id = 1 AND accepted_fg = 1 ORDER BY order_num LIMIT 5;
+----+-------------+--------+------+------------------------------------------+----------------+---------+-------------+------+-----------------------------+
| id | select_type | table  | type | possible_keys                            | key            | key_len | ref         | rows | Extra                       |
+----+-------------+--------+------+------------------------------------------+----------------+---------+-------------+------+-----------------------------+
|  1 | SIMPLE      | friend | ref  | PRIMARY,friend_order_uniq,friendship_idx | friendship_idx | 5       | const,const |    3 | Using where; Using filesort |
+----+-------------+--------+------+------------------------------------------+----------------+---------+-------------+------+-----------------------------+
1 row in set (0.00 sec)

friendship_idxを使ってfilesortが発生しています。嫌な感じですね。
この場合、friendship_idxによる絞込よりソートにindexを使って欲しいでしょう。
friend_order_uniqを使ってほしいところです。
こんなときにINDEX Hintが役立ちます。

mysql> EXPLAIN SELECT friend_user_id FROM friend FORCE INDEX (friend_order_uniq) WHERE user_id = 1 AND accepted_fg = 1 ORDER BY order_num LIMIT 5;
+----+-------------+--------+------+-------------------+-------------------+---------+-------+------+-------------+
| id | select_type | table  | type | possible_keys     | key               | key_len | ref   | rows | Extra       |
+----+-------------+--------+------+-------------------+-------------------+---------+-------+------+-------------+
|  1 | SIMPLE      | friend | ref  | friend_order_uniq | friend_order_uniq | 4       | const |    5 | Using where |
+----+-------------+--------+------+-------------------+-------------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

filesortが消えました。便利!
このように、場合によっては効率の悪いINDEXが選ばれるケースがあるのでINDEX HINTで使いたいINDEXを明示したほうが良いケースがあります。
ちなみに、 USE INDEXFORCE INDEX のどっちが良いかという話はあると思いますが、ぼくは答えを持ちあわせていません。すみません。
先日 @kamipo 先生に聞いたところによると、(ぼくの理解があっていれば)USE INEX だと指定したINDEXを使ってくれないケースがあるので、明らかに使って欲しいINDEXが決まっている場合は FORCE INDEX を使ったほうが良いという具合のことを教えて頂きました。
なので、そういう具合で使い分けていくのが良いのではないでしょうか!

余談

MySQL 5.7だと上記のケースで適切にINDEXを選んでくれてなかなか再現してくれなくて例題作るのにハマりました。

mysql> EXPLAIN SELECT friend_user_id FROM friend WHERE user_id = 1 AND accepted_fg = 1 ORDER BY order_num LIMIT 5;
+----+-------------+--------+------------+------+------------------------------------------+-------------------+---------+-------+------+----------+------------------------------------+
| id | select_type | table  | partitions | type | possible_keys                            | key               | key_len | ref   | rows | filtered | Extra                              |
+----+-------------+--------+------------+------+------------------------------------------+-------------------+---------+-------+------+----------+------------------------------------+
|  1 | SIMPLE      | friend | NULL       | ref  | PRIMARY,friend_order_uniq,friendship_idx | friend_order_uniq | 4       | const |    5 |    10.00 | Using index condition; Using where |
+----+-------------+--------+------------+------+------------------------------------------+-------------------+---------+-------+------+----------+------------------------------------+
1 row in set, 1 warning (0.00 sec)

MySQL 5.7すごい!

この投稿は MySQL Casual Advent Calendar 201513日目の記事です。