mysqlでindexの貼り方の検討がつかない時にやる事

mysqlでindexの貼り方の検討がつかない時にやる事

November 24, 2013,
tags: index mysql


このエントリーをはてなブックマークに追加

最近mysqlなDBのクエリーの見直しとかindexの見直しにひーひー言ってるポリドッグです。
上長にはindexの使われ方を想像しながら、indexを貼っていくんだ的な事をいわれて「(゚Д゚)ハァ」みたいに思ったわけです。
僕みたいなSQL嫌いなエンジニアからしたらイミフみたいな感じだったわけです。

そんなときに弊社CTOが教えてくれた
「indexの使われ方がわからないなら、すべてのカラムにindexを貼って、EXPLAINするといいよ」
これは今まで気づかなかったし、すごく良いアプローチだと思いました。

この事をもっと早く気づけていれば人生だいぶ違ったなぁーと。

たとえばこんなテーブルがあったとします。

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

テスト用のデータをインサート。

## 適当にインサートするためのストアドプロシージャ
DELIMITER //
CREATE PROCEDURE testInsert(IN max INT)
  BEGIN
  DECLARE cnt INT Default 1 ;
    simple_loop: LOOP
      INSERT INTO user (name, email, password, created) VALUES (CONCAT('user',cnt), CONCAT('test',cnt,'@test.com'),CONCAT('test_password',cnt),NOW());
      SET cnt = cnt+1;
      IF cnt=max THEN
        LEAVE simple_loop;
       END IF;
    END LOOP simple_loop;
END //

## インサート
CALL testInsert(1000000);

## ストアドプロシージャの削除
DROP PROCEDURE testInsert;

この何にもINDEXを貼っていないテーブルにたいして、ユーザー名で検索を行います。

mysql> SELECT * FROM `user` WHERE name = "user498361" AND email = "test498361@test.com";
+--------+------------+---------------------+---------------------+---------------------+---------------------+---------+
| id     | name       | email               | password            | created             | modified            | deleted |
+--------+------------+---------------------+---------------------+---------------------+---------------------+---------+
| 498361 | user498361 | test498361@test.com | test_password498361 | 2013-11-24 01:41:24 | 2013-11-24 01:41:24 | NULL    |
+--------+------------+---------------------+---------------------+---------------------+---------------------+---------+
1 row in set (0.70 sec)

0.7secとか遅過ぎて話になりませんね。。
ちなみにEXPLAINするとこんな感じです。

mysql> EXPLAIN SELECT * FROM `user` WHERE name = "user498361" AND email = "test498361@test.com";
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | user  | ALL  | NULL          | NULL | NULL    | NULL | 1000767 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
1 row in set (0.00 sec)

で、ここからが本題です。
まあこのクエリなら、name,emailの複合インデックスで問題ないとは思いますが、本当にそれが正しいのか確かめる為に、こんな感じにインデックスを貼ります。

ALTER TABLE `test`.`user` ADD INDEX `name_idx` (`name`);
ALTER TABLE `test`.`user` ADD INDEX `email_idx` (`email`);
ALTER TABLE `test`.`user` ADD INDEX `password_idx` (`password`);
ALTER TABLE `test`.`user` ADD INDEX `created_idx` (`created`);
ALTER TABLE `test`.`user` ADD INDEX `modified_idx` (`modified`);
ALTER TABLE `test`.`user` ADD INDEX `deleted_idx` (`deleted`);

この状態でEXPLAINを実行すると以下のようになりんす。

mysql> EXPLAIN SELECT * FROM `user` WHERE name = "user498361" AND email = "test498361@test.com";
+----+-------------+-------+------+--------------------+----------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys      | key      | key_len | ref   | rows | Extra       |
+----+-------------+-------+------+--------------------+----------+---------+-------+------+-------------+
|  1 | SIMPLE      | user  | ref  | name_idx,email_idx | name_idx | 767     | const |    1 | Using where |
+----+-------------+-------+------+--------------------+----------+---------+-------+------+-------------+
1 row in set (0.00 sec)

つまり、possible_keysにname_idxとemail_idxが含まれるというので、つまりname,eamilの複合indexを使用すれば良いという事になります。本当にそうなのか自信はないけど・・・

ということで複合インデックスを貼ります。

ALTER TABLE `test`.`user` ADD UNIQUE `name_eamil_idx` (`name`, `email`)

で、普通にクエリ投げてみます。

mysql> SELECT * FROM `user` WHERE name = "user498361" AND email = "test498361@test.com";
+--------+------------+---------------------+---------------------+---------------------+---------------------+---------+
| id     | name       | email               | password            | created             | modified            | deleted |
+--------+------------+---------------------+---------------------+---------------------+---------------------+---------+
| 498361 | user498361 | test498361@test.com | test_password498361 | 2013-11-24 01:41:24 | 2013-11-24 01:41:24 | NULL    |
+--------+------------+---------------------+---------------------+---------------------+---------------------+---------+
1 row in set (0.00 sec)

無事クエリが早くなりました。

ぶっちゃけ僕はMySQLが苦手なので、あれですが、こんな感じに最適なインデックスを探していくというのは良い方法かと思います。

comments powered by Disqus