ナクナイ

勉強用の備忘録

[Synergy] Synergy でキーボードがうまいこと効かない

Windows のキーボードやマウスで、Mac も操作したい!そういうときに使える Synergy というソフトがあります。
( Synergy については、調べるとたくさん出てくるので割愛します )

今回は、Synergy ではよくあるらしい、マウスはうまく連動するけどキーボードがうまく動かないという問題に直面したので、それの解決の備忘を残そうと思います。といっても、ぜんぜんたいしたことはしていません。

前提条件

サーバー:Windows7
クライアント:Mac OS X El Captain 10.11.3
synergy バージョン:Unknown と表示されて分かりませんでした。。5年前くらいなのでかなり古いです。すみません笑

設定出来たけどキーボードが全然効かない・・・

何を押しても反応しない。調べてみる。

sunagae.net

なんかそれっぽいところ見つけた!
さっそく「Unicode Hex Input」を追加して試してみたら、キーボードが効くようになった!

効くようになったけど記号がなんかおかしい・・・

喜んでたら、実は記号が全然うまく効いてないことに気づきました。
想定したものと全然違うの出てくる。。

そこで、「Unicode Hex Input」の代わりに「Australian」にしてみたらいけた。やったー。
原因はここらへんぽい。

calcurio.com

解決

Windows から Synergy 経由で mac を半角英字で操作するときは「Australian」を、
Mac から直接半角英字で操作するときはデフォルトの「英字」を選択すればOKです。
若干面倒くさいですが、とりあえず解決。



ちなみに、Synergy のバージョンがかなり古いものですが、ネットで調べてみる限り最近のバージョンでも同じ問題は起こっているようなので、同じような悩みがある人は試してみてください。

[perl] if とか defined とかの評価まとめ

perl の if とか defined とかの動作をまとめてみました。

                                                                                                                                                                  • +
if if defined print Dumper
$test; × × $VAR1 = undef;
$test = undef; × × $VAR1 = undef;
$test = ''; × $VAR1 = '';
$test = 0; × $VAR1 = 0;
$test = (); × × $VAR1 = undef;
$test = {}; $VAR1 = {};
$test = []; $VAR1 = [];
@test; × - $VAR1 = [];
@test = undef; - $VAR1 = [undef];
@test = ''; - $VAR1 = [''];
@test = 0; - $VAR1 = [0];
@test = (); × - $VAR1 = [];
@test = {}; - $VAR1 = [{}];
@test = []; - $VAR1 = [[]];
%test; × - $VAR1 = {};
%test = undef; - $VAR1 = {'' => undef};
%test = ''; - $VAR1 = {'' => undef};
%test = 0; - $VAR1 = {'0' => undef};
%test = (); × - $VAR1 = {};
%test = {}; - $VAR1 = {'HASH(0x166efc20)' => undef};
%test = []; - $VAR1 = {'ARRAY(0x166efc70)' => undef};
                                                                                                                                                                  • +

○・・・true
×・・・false
- ・・・エラー(※)

※ defined はスカラー関数にのみ有効なものだよ的なエラーが発生 (defined(@array) is deprecated / defined(%hash) is deprecated)
perldoc にも非推奨と記載があります Perlの組み込み関数 defined の翻訳 - perldoc.jp

ちなみに検証環境はv5.8.8です

[MySQL] GROUP_CONCAT 関数の結果をカラム別にして表示する

ある案件で、mysql の 便利関数 GROUP_CONCAT でまとめた複数レコードの情報を、カラム別にして表示したいことがありました。
GROUP_CONCAT 自体にそのような機能はありませんが、ちょっと工夫をすることで結果として出力したい結果を得ることができました。

こんな感じのテーブルが 2 つあるとします。

mysql> desc creep;
+-------------+------------------+------+-----+---------+----------------+
| Field       | Type             | Null | Key | Default | Extra          |
+-------------+------------------+------+-----+---------+----------------+
| id          | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| title       | varchar(10)      | NO   |     | NULL    |                |
| description | varchar(30)      | NO   |     | NULL    |                |
+-------------+------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

mysql> select * from creep;
+----+------------+----------------------+
| id | title      | description          |
+----+------------+----------------------+
|  1 | HE IS MINE | ライブで盛り上がるよ |
|  2 | 手と手     | かなり好きな曲だよ   |
|  3 | 傷つける   | 泣けるよ             |
+----+------------+----------------------+
3 rows in set (0.00 sec)
mysql> desc hyp;
+----------+---------------------+------+-----+---------+----------------+
| Field    | Type                | Null | Key | Default | Extra          |
+----------+---------------------+------+-----+---------+----------------+
| id       | int(10) unsigned    | NO   | PRI | NULL    | auto_increment |
| creep_id | int(10) unsigned    | NO   |     | NULL    |                |
| type     | tinyint(3) unsigned | NO   |     | NULL    |                |
+----------+---------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

mysql> select * from hyp;
+----+----------+------+
| id | creep_id | type |
+----+----------+------+
|  1 |        1 |    1 |
|  2 |        2 |    2 |
|  3 |        3 |    1 |
|  4 |        3 |    2 |
+----+----------+------+

creep テーブルを親として、hyp テーブルのレコードが子としてぶら下がります(creep.id = hyp.creep_id)
hyp テーブルの type には上記のように 1 か 2 が入り、creep テーブル 1 レコードに対して、 hyp テーブルには type=1 もしくは type=2 のレコードが入ります。

で、これをブラウザ上でこんな風に表示したくなりました。

                                                                                                                    • +
title description type=1 type=2
HE IS MINE ライブで盛り上がるよ -
手と手 かなり好きな曲だよ -
傷つける 泣けるよ
                                                                                                                    • +


これを実現するために、いくつかの SQL を試してみました。

とりあえず GROUP BY でまとめてみる

mysql> SELECT     creep.title,
    ->            creep.description,
    ->            hyp.type
    -> FROM       creep
    -> INNER JOIN hyp
    -> ON         creep.id = hyp.creep_id
    -> WHERE      1;

+------------+----------------------+------+
| title      | description          | type |
+------------+----------------------+------+
| HE IS MINE | ライブで盛り上がるよ |    1 |
| 手と手     | かなり好きな曲だよ   |    2 |
| 傷つける   | 泣けるよ             |    1 |
| 傷つける   | 泣けるよ             |    2 |
+------------+----------------------+------+
4 rows in set (0.00 sec)

当然、こんな感じに「傷つける」が 2 行になっちゃう。

GROUP_CONCAT を使う

mysql> SELECT     creep.title,
    ->            creep.description,
    ->            GROUP_CONCAT(hyp.type) AS types
    -> FROM       creep
    -> INNER JOIN hyp
    -> ON         creep.id = hyp.creep_id
    -> WHERE      1
    -> GROUP BY   hyp.creep_id;

+------------+----------------------+-------+
| title      | description          | types |
+------------+----------------------+-------+
| HE IS MINE | ライブで盛り上がるよ | 1     |
| 手と手     | かなり好きな曲だよ   | 2     |
| 傷つける   | 泣けるよ             | 2,1   |
+------------+----------------------+-------+
3 rows in set (0.00 sec)

受け取ったアプリケーション側で工夫が必要。

こんな方法を使うとカラム別に分けられる

mysql> SELECT     creep.title,
    ->            creep.description,
    ->            max(case hyp.type when 1 then 1 end) as type_1,
    ->            max(case hyp.type when 2 then 1 end) as type_2
    -> FROM       creep
    -> INNER JOIN hyp
    -> ON         creep.id = hyp.creep_id
    -> WHERE      1
    -> GROUP BY   hyp.creep_id;
+------------+----------------------+--------+--------+
| title      | description          | type_1 | type_2 |
+------------+----------------------+--------+--------+
| HE IS MINE | ライブで盛り上がるよ |      1 |   NULL |
| 手と手     | かなり好きな曲だよ   |   NULL |      1 |
| 傷つける   | 泣けるよ             |      1 |      1 |
+------------+----------------------+--------+--------+

hyp.creep_id でグルーピングしたレコード達の hyp.type の最大値が 1 なら type_1 に 1 を、最大値が2なら type_2 に 1 を立てる。といった使い方をしています。
グルーピングした結果、hyp.type がすべてNULL なら then に入らず NULL となります。


参考:
RDBの縦持ちテーブルと横持ちテーブル、およびその変換について




※ 今回のテーブル構成はわかりやすく簡易化しています。これくらいなら1テーブルにまとめればいいじゃんという突っ込みはナシでお願いします^^;

おしまい

[PHP]ファイルアップロードで $_FILE[***]['tmp_name']が空

動画をアップロードする機能を作った。

view 側はこんなかんじ。

<form action="/contents/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="upload_file">
    <input type="submit" value="決定">
</form>


そしたら $_FILES["upload_file"]["tmp_name"] が取れなくなることがあった。

で、PHP 側で $_FILES["upload_file"] を dump してみるとこんな感じ。

Array
(
    [upload_file] => Array
        (
            [name] => creephyp_daisuki.mov
            [type] => 
            [tmp_name] => 
            [error] => 1
            [size] => 0
        )

)

なんかアップロードに失敗しているっぽい。
error に 1 が入ってるので調べてみると、php.ini で設定されている「upload_max_filesize」という値より、アップロードファイルのサイズが超過していることが原因とのこと。

PHP: エラーメッセージの説明 - Manual


早速 phpinfo() で upload_max_filesize を確認。

<?php phpinfo(); ?>

を記述して確認してもいいし、実行サーバーで

$ php -r 'phpinfo();' | grep upload_max_filesize

のようにやってもOK。
確認したところ upload_max_filesize は 2M で設定されており、アップロードしようとしているファイルは見事に 2M 以上でした。

php.ini は、同じく phpinfo() の「Loaded Configuration File」の項目に書かれている場所にあるので、そこに書かれている php.ini を修正。
修正後は apache の再起動をすれば設定が反映されます。

反映後、再び $_FILES["upload_file"] を dump してみると無事アップロードできてることを確認。

Array
(
    [upload_file] => Array
        (
            [name] => creephyp_daisuki.mov
            [type] => video/3gpp2
            [tmp_name] => /tmp/phpUSl6ln
            [error] => 0
            [size] => 991956
        )

)

おしまい

[mysql] GRANT 構文で FLUSH PRIVILEGES をする必要はない

mysql で ユーザー権限の追加等で GRANT 文を発行したら flush することが当たり前といつの日からか思ってましたが、ぜんぜんそんなことありませんでした。


以下、公式ドキュメント
MySQL :: MySQL 5.1 リファレンスマニュアル :: 4.7.7 権限の変更が反映するタイミング
からの引用

GRANT, REVOKE, or SET PASSWORD などのステートメントを使用して、間接的に権限テーブルを変更する場合は、サーバがこれらの変更を認識し、その変更があった直後に権限テーブルをメモリへリロードします。

INSERT、UPDATE、DELETE などのステートメントを使用して、直接に権限テーブルを変更する場合は、サーバを再起動するか、またはテーブルのリロードを行なうまでその権限チェックは施行しません。手動で権限テーブルをリロードするには、FLUSH PRIVILEGES ステートメントを発行するか、mysqladmin flush-privileges または mysqladmin reload コマンドを実行します。

覚えておこ。。

cron が実行されない

手動でスクリプトを実行したときは問題なく動いたのに、crontab に設定するとなぜか動かないことってよくある。そんなときの備忘。

実行されているかどうか

まずはスクリプト内でエラーになっているのか、cron 自体が実行されていないのかの見極める。cron が実行されたり、crontab コマンドを発行したりすると、/var/log/cron にそのログが出力される。実行されるべき時間になってもログが出力されないようなら、下記コマンドを発行し、crond が動いているか確認する。

まずはプロセスの存在チェック

$ ps aux | grep cron
root      3028  0.0  0.0  74876  1168 ?        Ss    2014   0:00 crond
root     21887  0.0  0.0   6024   608 pts/3    S+   17:43   0:00 grep cron

もしくは

$ /etc/rc.d/init.d/crond status 
crond (pid  xxxx) を実行中...


つぎにプロセスの状態チェック

ランレベル 2 ~ 5 が ON になっているか確認 (参考:cron の設定ガイド)

$ chkconfig --list crond 
crond           0:off   1:off   2:on    3:on    4:on    5:on    6:off


最後に crontab の記述をチェック
プロセスが正しく起動している場合は、crontab の記述ミスを疑ってみる

cron 自体は実行されるけど、スクリプトが実行された形跡がない

上記チェックで問題なく、/var/log/cron にもログが出力されているのに正しく動作しない場合は、下記項目を疑ってみる。

パーミッション
まずはパーミッションを疑ってみる。cron で実行するユーザーと、手動で実行するユーザーが同じとは限らない。スクリプトファイルのパーミッションが cron で設定しているユーザーで実行できるか確認し、必要であればパーミッションを変更する。また、スクリプト自体が実行できたとしても、たとえばスクリプトの中でファイル生成などを行っている場合は、そこのパーミッションで失敗する場合もあるので注意が必要。

文字コード周り
UTF-8 の BOM 付きでファイルを保存していた場合、先頭行のインタプリタ(たとえば perl なら #! /usr/bin/perl -w の部分)が正しく認識されず、正しく動作しないことがある。この場合は、BOM を外すなどして対応する。vim の場合、下記を実行することで BOM をつけたり外したりすることが可能。

BOM つきにする

:set fenc=utf-8 bomb

BOM なしにする

:set nobomb

また、バイナリモードで表示する -b オプションを使用してファイルを開くと、BOM 付きかどうか確認できる (feff の部分が BOM)。

<feff>#! /usr/bin/perl -w
use strict;

# ファイル保存
open my $fh, ">", "/home/tamulapin/badfeeling.txt" or die "error:$!";
    print $fh rand(10);
    print $fh "\n";
close $fh;

vim 以外で BOM の確認とか追加/削除がしたい場合はここら辺(コマンドラインでUTF-8テキストのBOMを追加したり削除したりする | skmks) が参考になりそう。そのほか、改行コードが CR+LF になっていれば LF にするなど試してみるといいかもしれない。

それでも原因が分からない

どうしても分からなければログを出力しちゃう。crontab の最後に、実行結果をファイル出力しちゃえば原因が特定できる場合がある。

* * * * * /usr/bin/perl /home/tamulapin/waraw.pl >>/tmp/exec.log 2>>/tmp/exec_error.log


これらを試してみればたいていは解決するはず。

おしまい

[mysql] ORDER BY が効かない

mysql を使っていて、条件によって ORDER BY が効かない状況があった。こんな記事 (MySQL - select文でorder by句が正しく効かない場合がある - Qiita)もあったけど今回の事象とは違う感じなので、自分なりに調べたことを備忘。

とりあえず、先に調べたことのまとめ


InnoDB で、セカンダリインデックスを使用する ORDER BY クラスタインデックス (プライマリーキー) の SQL は正しく動作しないことがある
・ただし、Covering Index であれば正しく挙動する。
・Covering Index を使えない場合 (SELECT * で実行したい場合) は、セカンダリインデックスをプライマリーキーとの複合インデックスにすれば OK
・ただ、InnoDB のセカンダリインデックスはリーフノードにプライマリーキーの値を保持しているので、どうしてこのような挙動になるかは謎


環境

mysql バージョン

mysql> SELECT version();
+-----------+
| version() |
+-----------+
| 5.0.67    |
+-----------+
1 row in set (0.00 sec)


テーブル構成

mysql> DESC guitarhythm;
+-------------+---------------------+------+-----+-------------------+----------------+
| Field       | Type                | Null | Key | Default           | Extra          |
+-------------+---------------------+------+-----+-------------------+----------------+
| id          | int(10) unsigned    | NO   | PRI | NULL              | auto_increment |
| name        | varchar(10)         | NO   |     | NULL              |                |
| valid_flag  | tinyint(3) unsigned | NO   | MUL | 0                 |                |
| type        | tinyint(3) unsigned | NO   | MUL | 0                 |                |
| insert_time | datetime            | NO   |     | NULL              |                |
| update_time | timestamp           | NO   |     | CURRENT_TIMESTAMP |                |
+-------------+---------------------+------+-----+-------------------+----------------+
6 rows in set (0.00 sec)


インデックス情報

mysql> SHOW INDEX FROM guitarhythm;
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table       | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| guitarhythm |          0 | PRIMARY    |            1 | id          | A         |           2 |     NULL | NULL   |      | BTREE      |         |
| guitarhythm |          1 | valid_flag |            1 | valid_flag  | A         |           2 |     NULL | NULL   |      | BTREE      |         |
| guitarhythm |          1 | type       |            1 | type        | A         |           2 |     NULL | NULL   |      | BTREE      |         |
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
3 rows in set (0.00 sec)

レコード内容

mysql> SELECT * FROM guitarhythm WHERE 1;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
|  2 | strat    |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  3 | les      |          0 |    7 | 2015-01-09 12:06:27 | 2015-01-09 13:33:45 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
|  5 | fire     |          0 |    5 | 2015-01-09 12:06:29 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
+----+----------+------------+------+---------------------+---------------------+
6 rows in set (0.00 sec)


ストレージエンジン

InnoDB を使用。

ORDER BY が効かないクエリを発見


[Case1] id で order。limit なし ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
|  2 | strat    |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
4 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC;
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | guitarhythm | index | valid_flag    | PRIMARY | 4       | NULL |    5 | Using WHERE |
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)

[Case2] id で order。limit 100 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 100;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
|  2 | strat    |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
4 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 100;
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | guitarhythm | index | valid_flag    | PRIMARY | 4       | NULL |    5 | Using WHERE |
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)

[Case3] id で order。limit 3 ... NG

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+---------+------------+------+---------------------+---------------------+
| id | name    | valid_flag | type | insert_time         | update_time         |
+----+---------+------------+------+---------------------+---------------------+
|  1 | tele    |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
|  2 | strat   |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  4 | Mocking |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
+----+---------+------------+------+---------------------+---------------------+
3 rows in set (0.02 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
|  1 | SIMPLE      | guitarhythm | range | valid_flag    | valid_flag | 1       | NULL |    3 | Using WHERE |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.00 sec)

パターン3 がおかしい!なんで???全然検討がつかない。。。

id (primary key) 以外のカラムを指定してソートしてみる

インデックスを設定していないカラム (insert_time)

[Case4] insert_time で order。limit なし ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
|  2 | strat    |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
4 rows in set (0.01 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC;
+----+-------------+-------------+------+---------------+------+---------+------+------+-----------------------------+
| id | select_type | table       | type | possible_keys | key  | key_len | ref  | rows | Extra                       |
+----+-------------+-------------+------+---------------+------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | ALL  | valid_flag    | NULL | NULL    | NULL |    5 | Using WHERE; Using filesort |
+----+-------------+-------------+------+---------------+------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)

[Case5] insert_time で order。limit 100 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC LIMIT 100;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
|  2 | strat    |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
4 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC LIMIT 100;
+----+-------------+-------------+------+---------------+------+---------+------+------+-----------------------------+
| id | select_type | table       | type | possible_keys | key  | key_len | ref  | rows | Extra                       |
+----+-------------+-------------+------+---------------+------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | ALL  | valid_flag    | NULL | NULL    | NULL |    5 | Using WHERE; Using filesort |
+----+-------------+-------------+------+---------------+------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)

[Case6] insert_time で order。limit 3 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC LIMIT 3;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
|  2 | strat    |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
+----+----------+------------+------+---------------------+---------------------+
3 rows in set (0.00 sec)
mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC LIMIT 3;
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-----------------------------+
| id | select_type | table       | type  | possible_keys | key        | key_len | ref  | rows | Extra                       |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | range | valid_flag    | valid_flag | 1       | NULL |    3 | Using WHERE; Using filesort |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)

これは、全部期待どおりの動作


セカンダリインデックスのカラム (type)

[Case7] type で order。limit なし ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  2 | strat    |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
4 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC;
+----+-------------+-------------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | guitarhythm | index | valid_flag    | type | 1       | NULL |    5 | Using WHERE |
+----+-------------+-------------+-------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

[Case8] type で order。limit 100 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC LIMIT 100;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  2 | strat    |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
4 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC LIMIT 100;
+----+-------------+-------------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | guitarhythm | index | valid_flag    | type | 1       | NULL |    5 | Using WHERE |
+----+-------------+-------------+-------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

[Case9] type で order。limit 3 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC LIMIT 3;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  2 | strat    |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
+----+----------+------------+------+---------------------+---------------------+
3 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC LIMIT 3;
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-----------------------------+
| id | select_type | table       | type  | possible_keys | key        | key_len | ref  | rows | Extra                       |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | range | valid_flag    | valid_flag | 1       | NULL |    3 | Using WHERE; Using filesort |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)

この場合も全部期待どおりの動作。てことは、プライマリーキーで ORDER BY したときに変な動きになることがあるってこと??どういう条件でこうなるんだろう・・・・

件数ちょっと増やしてみた(6件→10件)

変更後のレコード内容

mysql> SELECT * FROM guitarhythm WHERE 1;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
|  2 | strat    |          1 |    2 | 2015-01-09 12:06:26 | 2015-01-09 13:31:58 |
|  3 | les      |          0 |    7 | 2015-01-09 12:06:27 | 2015-01-09 13:33:45 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
|  5 | fire     |          0 |    5 | 2015-01-09 12:06:29 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  8 | hide     |          0 |    9 | 2015-01-09 12:06:32 | 2015-01-09 13:33:45 |
|  9 | himuro   |          0 |    1 | 2015-01-09 12:06:33 | 2015-01-09 13:33:45 |
| 10 | ore      |          0 |    3 | 2015-01-09 12:06:34 | 2015-01-09 13:33:56 |
+----+----------+------------+------+---------------------+---------------------+
10 rows in set (0.00 sec)

この状態で、上記 9 パターンを全て試してみる


[Case1] id で order。limit なし ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
|  2 | strat    |          1 |    2 | 2015-01-09 12:06:26 | 2015-01-09 13:31:58 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
5 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    5 | Using WHERE |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

[Case2] id で order。limit 100 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 100;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
|  2 | strat    |          1 |    2 | 2015-01-09 12:06:26 | 2015-01-09 13:31:58 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
5 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 100;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    5 | Using WHERE |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

[Case3] id で order。limit 3 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
+----+----------+------------+------+---------------------+---------------------+
3 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    5 | Using WHERE |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

[Case4] insert_time で order。limit なし ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
|  2 | strat    |          1 |    2 | 2015-01-09 12:06:26 | 2015-01-09 13:31:58 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
5 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra                       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    5 | Using WHERE; Using filesort |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)

[Case5] insert_time で order。limit 100 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC LIMIT 100;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
|  2 | strat    |          1 |    2 | 2015-01-09 12:06:26 | 2015-01-09 13:31:58 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
5 rows in set (0.01 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC LIMIT 100;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra                       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    5 | Using WHERE; Using filesort |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)

[Case6] insert_time で order。limit 3 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC LIMIT 3;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
+----+----------+------------+------+---------------------+---------------------+
3 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY insert_time DESC LIMIT 3;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra                       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    5 | Using WHERE; Using filesort |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)

[Case7] type で order。limit なし ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  2 | strat    |          1 |    2 | 2015-01-09 12:06:26 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
5 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra                       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    5 | Using WHERE; Using filesort |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)

[Case8] type で order。limit 100 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC LIMIT 100;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  2 | strat    |          1 |    2 | 2015-01-09 12:06:26 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
+----+----------+------------+------+---------------------+---------------------+
5 rows in set (0.00 sec)


mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC LIMIT 100;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra                       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    5 | Using WHERE; Using filesort |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)

[Case9] type で order。limit 3 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC LIMIT 3;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  2 | strat    |          1 |    2 | 2015-01-09 12:06:26 | 2015-01-09 13:31:58 |
+----+----------+------------+------+---------------------+---------------------+
3 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY type DESC LIMIT 3;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra                       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    5 | Using WHERE; Using filesort |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)

全部期待通りの動き。とりあえずこれらの結果から、「プライマリーキーでソートするとき、ある条件を満たすと ORDER BY が正しく動かない」だろうという仮説を立てる。

改めてレコードを増やす前と後の結果に注目してみる

見るのは、結果に差が出た [Case3] のもの。

レコード数が6のときの結果 ... NG

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+---------+------------+------+---------------------+---------------------+
| id | name    | valid_flag | type | insert_time         | update_time         |
+----+---------+------------+------+---------------------+---------------------+
|  1 | tele    |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
|  2 | strat   |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  4 | Mocking |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
+----+---------+------------+------+---------------------+---------------------+
3 rows in set (0.02 sec)
mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
|  1 | SIMPLE      | guitarhythm | range | valid_flag    | valid_flag | 1       | NULL |    3 | Using WHERE |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.00 sec)

レコード数が 10 のときの結果 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  7 | hotei    |          1 |    7 | 2015-01-09 12:06:31 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
+----+----------+------------+------+---------------------+---------------------+
3 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    5 | Using WHERE |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

EXPLAIN を見ると、同じインデックスキーが使われてるけど、typeが違う。うまくいかないときは range になっている。ん?そもそも、なんで type が range なんだろう。range は範囲指定のはず。
参考:漢(オトコ)のコンピュータ道: MySQLのEXPLAINを徹底解説!!

いや、そうは言ってもユニークインデックスではない限り、オプティマイザが range でスキャンする可能性はあるのか。
参考:INDEX RANGE SCAN とは? 【OKWave】

今回の原因とは関係なさそう。

ためしに、カラムを指定して SELECT してみる

[Case3] でうまくいかなかった 6 件の状態に戻す。

mysql> SELECT * FROM guitarhythm WHERE 1;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  1 | tele     |          1 |    1 | 2015-01-09 12:06:25 | 2015-01-09 12:27:57 |
|  2 | strat    |          1 |    3 | 2015-01-09 12:06:26 | 2015-01-09 13:45:24 |
|  3 | les      |          0 |    7 | 2015-01-09 12:06:27 | 2015-01-09 13:33:45 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:45:24 |
|  5 | fire     |          0 |    5 | 2015-01-09 12:06:29 | 2015-01-09 13:31:58 |
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
+----+----------+------------+------+---------------------+---------------------+
6 rows in set (0.00 sec)


この状態で、SELECT するカラムを指定して [Case3] の条件でクエリを発行してみる。


プライマリーキーの id で検索 ... OK

mysql> SELECT id FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+
| id |
+----+
|  6 |
|  4 |
|  2 |
+----+
3 rows in set (0.00 sec)

mysql> EXPLAIN SELECT id FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+--------------------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra                    |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    3 | Using where; Using index |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

期待通りの動作。valid_flag インデックスを使っていて、Extra が Using index になっているところをみると、Covering Index が使われていることが分かる。これは、クエリがインデックスだけを用いて解決できていることを示している(Covering Index について: http://nippondanji.blogspot.jp/2010/10/innodb.html)

セカンダリインデックスの valid_flag で検索 (ソートが分かるように id もくっつける) ... OK

mysql> SELECT valid_flag, id FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+------------+----+
| valid_flag | id |
+------------+----+
|          1 |  6 |
|          1 |  4 |
|          1 |  2 |
+------------+----+
3 rows in set (0.00 sec)

mysql> EXPLAIN SELECT valid_flag, id FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+--------------------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra                    |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    3 | Using where; Using index |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

期待通りの動作。こちらも Covering Index が使われていることが分かる。


セカンダリインデックスの type で検索 (ソートが分かるように id もくっつける) ... NG

mysql> SELECT type, id FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+------+----+
| type | id |
+------+----+
|    1 |  1 |
|    2 |  2 |
|    2 |  4 |
+------+----+
3 rows in set (0.00 sec)

mysql> EXPLAIN SELECT type, id FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
|  1 | SIMPLE      | guitarhythm | range | valid_flag    | valid_flag | 1       | NULL |    3 | Using where |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.00 sec)

期待していない動作


インデックスを貼っていない insert_time で検索 (ソートが分かるように id もくっつける) ... NG

mysql> SELECT insert_time, id FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+---------------------+----+
| insert_time         | id |
+---------------------+----+
| 2015-01-09 12:06:25 |  1 |
| 2015-01-09 12:06:26 |  2 |
| 2015-01-09 12:06:28 |  4 |
+---------------------+----+
3 rows in set (0.00 sec)

mysql> EXPLAIN SELECT insert_time, id FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
|  1 | SIMPLE      | guitarhythm | range | valid_flag    | valid_flag | 1       | NULL |    3 | Using where |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.01 sec)

こちらも期待していない動作。うむむむ。見た感じ、Covering Index が使用されていないときにエラーになっているみたい。

なんとなく valid_flag の単独インデックスを、valid_flag と id の複合インデックスにしてみた

こんな感じに設定。

mysql> SHOW INDEX FROM guitarhythm;
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table       | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| guitarhythm |          0 | PRIMARY    |            1 | id          | A         |           2 |     NULL | NULL   |      | BTREE      |         |
| guitarhythm |          1 | type       |            1 | type        | A         |           2 |     NULL | NULL   |      | BTREE      |         |
| guitarhythm |          1 | valid_flag |            1 | valid_flag  | A         |           2 |     NULL | NULL   |      | BTREE      |         |
| guitarhythm |          1 | valid_flag |            2 | id          | A         |           2 |     NULL | NULL   |      | BTREE      |         |
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
4 rows in set (0.00 sec)


レコードが6件の状態で、[Case3] のクエリを実行 ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
|  2 | strat    |          1 |    2 | 2015-01-09 12:06:26 | 2015-01-09 13:31:58 |
+----+----------+------------+------+---------------------+---------------------+
3 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
|  1 | SIMPLE      | guitarhythm | range | valid_flag    | valid_flag | 1       | NULL |    3 | Using WHERE |
+----+-------------+-------------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.00 sec)

想定どおりの動作。なんでだろう。しかも、EXPLAIN 結果が、単独インデックスで正しく動かなかったときとまったく同じ・・・InnoDB のセカンダリインデックスのリーフノードにはプライマリーキーの値が保持されているから、valid_flag のインデックスと valid_flag, id の複合インデックスは同じで、動作もまったく同じという認識だったんだけど・・・
参考:
漢(オトコ)のコンピュータ道: 知って得するInnoDBセカンダリインデックス活用術!

でも、実際に返ってきている結果が変わっているということは、何かしら違いがあるということか。よく分からないけど、とりあえずインデックスが今回の事象を紐解くヒントがあるのでは??というあたりはなんとなく付いてきた。

InnoDB とはインデックスの仕様が異なる MyISAM を試してみる

ストレージエンジンを MyISAM に変更し、InnoDB でうまくいかなった状態(件数は 6 件で、valid_flag は単独インデックス)にし、[Case3] をやってみる ... OK

mysql> SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+----------+------------+------+---------------------+---------------------+
| id | name     | valid_flag | type | insert_time         | update_time         |
+----+----------+------------+------+---------------------+---------------------+
|  6 | zemaitis |          1 |    6 | 2015-01-09 12:06:30 | 2015-01-09 13:31:58 |
|  4 | Mocking  |          1 |    2 | 2015-01-09 12:06:28 | 2015-01-09 13:33:45 |
|  2 | strat    |          1 |    2 | 2015-01-09 12:06:26 | 2015-01-09 13:31:58 |
+----+----------+------------+------+---------------------+---------------------+
3 rows in set (0.00 sec)

mysql> explain SELECT * FROM guitarhythm WHERE valid_flag = 1 ORDER BY id DESC LIMIT 3;
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
| id | select_type | table       | type | possible_keys | key        | key_len | ref   | rows | Extra                       |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
|  1 | SIMPLE      | guitarhythm | ref  | valid_flag    | valid_flag | 1       | const |    3 | Using WHERE; Using filesort |
+----+-------------+-------------+------+---------------+------------+---------+-------+------+-----------------------------+
1 row in set (0.00 sec)


期待どおりの動作!ということは、やっぱり InnoDB のセカンダリインデックス周りに原因があるみたい。

改めて、まとめ。


InnoDB で、セカンダリインデックスを使用する ORDER BY クラスタインデックス (プライマリーキー) の SQL は正しく動作しないことがある
・ただし、Covering Index であれば正しく挙動する。
・Covering Index を使えない場合 (SELECT * で実行したい場合) は、セカンダリインデックスをプライマリーキーとの複合インデックスにすれば OK
・ただ、InnoDB のセカンダリインデックスはリーフノードにプライマリーキーの値を保持しているので、どうしてこのような挙動になるかは謎


謎がいっぱい。もっと勉強しなくちゃ・・・