MySQLのLIMIT

知り合いよりMySQLの使い方がひどいんですって話があって、その中でLIMITの話があったのでこういう事なのかなーと推測してみた。

その話によるとなんでもかんでもLIMITをつけている。SELECT * FROM XXXX LIMIT 1 みたいな事をいろんなところでやっているという事でした。MySQLはそんなに詳しくないのでDB2のOPTIMIZE FOR 1 ROWみたいなおまじないでつけてるんじゃないのかなーと思いつつ、実際に実行計画をとってみました。

テーブルを専用に用意するのは面倒なので、家のテーブルから適当にデータが入っているやつをチョイス。

mysql> describe BC2_ST_POSTLIST;
+------------+---------------+------+-----+-------------------+-----------------------------+
| Field      | Type          | Null | Key | Default           | Extra                       |
+------------+---------------+------+-----+-------------------+-----------------------------+
| HASH       | bigint(20)    | NO   | MUL | NULL              |                             |
| LINK       | varchar(1024) | NO   |     | NULL              |                             |
| STATUS     | int(11)       | NO   |     | 0                 |                             |
| REGISTDATE | timestamp     | NO   | MUL | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+------------+---------------+------+-----+-------------------+-----------------------------+
4 rows in set (0.00 sec)
mysql> show index from BC2_ST_POSTLIST;
+-----------------+------------+-----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table           | Non_unique | Key_name              | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-----------------+------------+-----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| BC2_ST_POSTLIST |          1 | IDX_BC2_ST_POSTLIST   |            1 | HASH        | A         |     4166553 |     NULL | NULL   |      | BTREE      |         |
| BC2_ST_POSTLIST |          1 | IDX_BC2_ST_POSTLIST_2 |            1 | REGISTDATE  | A         |       41253 |     NULL | NULL   |      | BTREE      |         |
+-----------------+------------+-----------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
2 rows in set (0.02 sec)
mysql> select count(*) from BC2_ST_POSTLIST;
+----------+
| count(*) |
+----------+
|  4164791 |
+----------+
1 row in set (0.00 sec)

これにSQLを発行してみる。

mysql> explain select * from BC2_ST_POSTLIST LIMIT 1;
+----+-------------+-----------------+------+---------------+------+---------+------+---------+-------+
| id | select_type | table           | type | possible_keys | key  | key_len | ref  | rows    | Extra |
+----+-------------+-----------------+------+---------------+------+---------+------+---------+-------+
|  1 | SIMPLE      | BC2_ST_POSTLIST | ALL  | NULL          | NULL | NULL    | NULL | 4166649 |       |
+----+-------------+-----------------+------+---------------+------+---------+------+---------+-------+
1 row in set (0.00 sec)

おや?rowsがひどいことになってます。(件数が上のCOUNTと違うのはオンラインで稼働中のテーブルなので・・)。これではLIMITをつけない場合と同じですね。OracleのCount Stopのような実行計画になると思っていました。

MySQLのリファレンスによると、色々かかれていますね。
http://dev.mysql.com/doc/refman/5.1/ja/limit-optimization.html

試しにORDER BYをINDEXをつけている列につけてみました。

mysql> explain select * from BC2_ST_POSTLIST ORDER BY REGISTDATE LIMIT 1;
+----+-------------+-----------------+-------+---------------+-----------------------+---------+------+------+-------+
| id | select_type | table           | type  | possible_keys | key                   | key_len | ref  | rows | Extra |
+----+-------------+-----------------+-------+---------------+-----------------------+---------+------+------+-------+
|  1 | SIMPLE      | BC2_ST_POSTLIST | index | NULL          | IDX_BC2_ST_POSTLIST_2 | 4       | NULL |    1 |       |
+----+-------------+-----------------+-------+---------------+-----------------------+---------+------+------+-------+
1 row in set (0.00 sec)

rowsが1件になりました。
というか、そもそもWHEREやORDER BYを指定しないクエリにLIMITをつける意味が私にはわかりませんでした(´・ω・)
意味というか、それで要件を満たせるユースケースってどんなのがあるかなーと。
ただし、

MySQL が要求された行数をクライアントに送信すると、クエリが中止されます(SQL_CALC_FOUND_ROWSを使用していない場合)。

とあるので、この場合は1件フェッチした場合にクエリが中断されるんだと思います。それを確認してみました。

mysql> explain select SQL_CALC_FOUND_ROWS * from BC2_ST_POSTLIST ORDER BY REGISTDATE LIMIT 1;
+----+-------------+-----------------+------+---------------+------+---------+------+---------+----------------+
| id | select_type | table           | type | possible_keys | key  | key_len | ref  | rows    | Extra          |
+----+-------------+-----------------+------+---------------+------+---------+------+---------+----------------+
|  1 | SIMPLE      | BC2_ST_POSTLIST | ALL  | NULL          | NULL | NULL    | NULL | 4174728 | Using filesort |
+----+-------------+-----------------+------+---------------+------+---------+------+---------+----------------+
1 row in set (0.00 sec)

ふむふむ。実際にEXPLAINじゃなくて実行してみた。
データの中身は見せられないので切り取り。

mysql> select SQL_NO_CACHE SQL_CALC_FOUND_ROWS * from BC2_ST_POSTLIST LIMIT 1;
1 row in set (5.20 sec)
mysql> select SQL_NO_CACHE * from BC2_ST_POSTLIST LIMIT 1;
1 row in set (0.00 sec)

ふむふむ。ということはやっぱり中断はされているっぽいですね。
MySQLってOracleのように何バイト読み込んだかみたいなものって見れないのかな・・。

いいお勉強になりました。
うーん。これくらいじゃ目くじら立てる原因にはならなそうなので他の要因がありそうです(まぁ積もりにつもった様々なイライラだと思いますがw)。テーブルの全データをメモリに載せようとしちゃうのかな。実行計画上は全件スキャンするようになっているし。
これ以上は知識が無いのでソース読んだ方が早そう(´・ω・)

まぁテーブル構造知らないしどんな要件でどんなSQLを発行しているのか分からないので、なんとも言えませんでした(;´Д`)

 

新しいサイトもよろしくお願いします!