MySQL InnoDBだけで全文検索

実験エントリです。

作ってみる

とりあえず、Twitter4Jを使ってこんなデータを用意しました。ちなみに人選は漢(オトコ)のコンピュータ道: MySQLerのTwitterアカウントまとめ。を参考にさせていただきました。

5707049458,2009-11-14 20:28:34,sakaik,@hbstudy  あ。そうですか〜。教えていただき
5707129844,2009-11-14 20:35:11,kazuho,InnoDB 1.0.5でバッファプールのGenerational
5707141572,2009-11-14 20:36:09,kazuho,RT @higepon: RT @yukichi: 「フリーランス」
5707146470,2009-11-14 20:36:34,_Fake,@_yuyu どうしてこうなった
5707159628,2009-11-14 20:37:41,hirose31,恵比寿でスーツコスなう
5707167760,2009-11-14 20:38:22,kazuho,old領域はデフォルトで37%。ロードしたデータ
5707190203,2009-11-14 20:40:17,stanaka,色々カスタマイズ可能な項目が増えるのは基
5707246432,2009-11-14 20:45:00,sh2nd,いきたかった #hbstudy
5707324541,2009-11-14 20:51:27,kazuho,mtstat は Monty Taylor's stat の略なのかな
5707385902,2009-11-14 20:56:23,_Fake,ブレーカー落ちた…

次に、タイムラインを格納するためのテーブルを作ります。

mysql> DESC timeline;
+-------------+--------------+------+-----+---------+-------+
| Field       | Type         | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| id          | bigint(20)   | NO   | PRI | NULL    |       |
| created_at  | datetime     | YES  |     | NULL    |       |
| screen_name | varchar(15)  | YES  |     | NULL    |       |
| text        | varchar(140) | YES  |     | NULL    |       |
+-------------+--------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

もう一つテーブルを作ります。こういうのを転置インデックスと言うんだそうです。

mysql> DESC timeline_i;
+--------+------------+------+-----+---------+-------+
| Field  | Type       | Null | Key | Default | Extra |
+--------+------------+------+-----+---------+-------+
| bigram | varchar(2) | NO   | PRI |         |       |
| id     | bigint(20) | NO   | PRI | 0       |       |
+--------+------------+------+-----+---------+-------+

timelineテーブルにトリガを仕掛けます。

DROP TRIGGER IF EXISTS timeline_create_bigram;
DELIMITER //
CREATE TRIGGER timeline_create_bigram
AFTER INSERT ON timeline FOR EACH ROW
BEGIN
 DECLARE text_length INT;
 DECLARE text_index INT;
 
 SET text_length = CHAR_LENGTH(NEW.text);
 SET text_index = 1;
 
 WHILE text_index < text_length DO
  INSERT IGNORE INTO timeline_i (bigram, id)
         VALUES (SUBSTRING(NEW.text, text_index, 2), NEW.id);
  SET text_index = text_index + 1;
 END WHILE;
END;
//
DELIMITER ;

データロードを行います。

mysql> LOAD DATA LOCAL INFILE 'timeline.dat' INTO TABLE timeline;
Query OK, 35732 rows affected (1 min 34.76 sec)
Records: 35732  Deleted: 0  Skipped: 0  Warnings: 0

だいぶ遅いですけど、うまくロードできたみたいです。データロード後のテーブルは以下のようになります。

mysql> SELECT id, created_at, screen_name, LEFT(text, 20)
    -> FROM timeline ORDER BY id LIMIT 10 OFFSET 32000;
+------------+---------------------+-------------+---------------------------------------+
| id         | created_at          | screen_name | LEFT(text, 20)                        |
+------------+---------------------+-------------+---------------------------------------+
| 5706909826 | 2009-11-14 20:16:42 | sakaik      | 参加できなかったんだけど、ustないのん |
| 5706917932 | 2009-11-14 20:17:26 | kazuho      | オチを先に言うの禁止www #hbstu        |
| 5706918515 | 2009-11-14 20:17:30 | _Fake       | @_yuyu もうだまされないぞ!           |
| 5706994272 | 2009-11-14 20:23:52 | _Fake       | @_yuyu 日本語でお願いします!         |
| 5707049458 | 2009-11-14 20:28:34 | sakaik      | @hbstudy  あ。そうですか〜。教        |
| 5707129844 | 2009-11-14 20:35:11 | kazuho      | InnoDB 1.0.5でバッファプール          |
| 5707141572 | 2009-11-14 20:36:09 | kazuho      | RT @higepon: RT @yuk                  |
| 5707146470 | 2009-11-14 20:36:34 | _Fake       | @_yuyu どうしてこうなった             |
| 5707159628 | 2009-11-14 20:37:41 | hirose31    | 恵比寿でスーツコスなう                |
| 5707167760 | 2009-11-14 20:38:22 | kazuho      | old領域はデフォルトで37%。ロードし    |
+------------+---------------------+-------------+---------------------------------------+
10 rows in set (0.04 sec)

mysql> SELECT bigram, id FROM timeline_i
    -> ORDER BY bigram, id LIMIT 20 OFFSET 1280000;
+--------+------------+
| bigram | id         |
+--------+------------+
| 行性   | 2627699213 |
| 行情   | 4696663607 |
| 行手   | 6165165693 |
| 行振   | 1968230572 |
| 行支   | 5609167583 |
| 行支   | 5751891393 |
| 行政   | 4309254243 |
| 行政   | 5793749538 |
| 行数   |  814006785 |
| 行数   |  895299155 |
| 行数   | 3836569341 |
| 行数   | 3931668910 |
| 行数   | 4496147540 |
| 行方   |  773875969 |
| 行方   | 3121814754 |
| 行方   | 3976865730 |
| 行日   | 6055373012 |
| 行時   |  802293533 |
| 行時   | 3275845988 |
| 行時   | 3275991644 |
+--------+------------+
20 rows in set (0.75 sec)

検索してみる

普通にLIKE検索してみると、こんな感じです。

mysql> SELECT id, created_at, screen_name, text FROM timeline
    -> WHERE text LIKE '%味噌ラーメン%'\G
*************************** 1. row ***************************
         id: 5007144696
 created_at: 2009-10-20 10:46:49
screen_name: hirose31
       text: @sugizou 昼はねぎ味噌ラーメンにしまっす!
1 row in set (0.07 sec)

転置インデックスを組み合わせると、こうなります。

mysql> SELECT t.id, t.created_at, t.screen_name, t.text
    -> FROM timeline t INNER JOIN timeline_i i ON t.id = i.id
    -> WHERE i.bigram = '味噌'
    -> AND t.text LIKE '%味噌ラーメン%'\G
*************************** 1. row ***************************
         id: 5007144696
 created_at: 2009-10-20 10:46:49
screen_name: hirose31
       text: @sugizou 昼はねぎ味噌ラーメンにしまっす!
1 row in set (0.00 sec)

おー、速くなっているみたいですね!

測定してみる

適当に作ったわりには効果があるようなので、きちんと測定してみました。

  • 試験プログラム:Javaで自作
  • 同時接続数:1
  • 試験方法:ネットワーク越しにクエリを連続実行し、レスポンスタイムの中央値を取得
  • CPU:Pentium4 2.8GHz HTなし
  • RAM:DDR-266 SDRAM 1GB
  • HDD:IDE 3.5inch 7,200rpm 80GB
  • OS:CentOS 5.4 32bit
  • RDBMSMySQL Community Server 5.1.41+InnoDB Plugin 1.0.5

今まではVMwareの環境を使っていたのですが、ベンチマークをするのに仮想化はないだろうということで古いPCを引っ張り出してきました。しばらくこれを使っていこうと思います。
いろいろな検索語で試してみたところ、以下のような結果になりました。

検索語 ヒット件数 転置インデックス LIKE検索
味噌ラーメン 1件 1msec 85msec
チャーハン 9件 3msec 85msec
PostgreSQL 28件 10msec 88msec
チューニング 32件 2msec 85msec
バックアップ 70件 6msec 87msec
Oracle 87件 20msec 89msec
Java 117件 9msec 86msec
Drizzle 176件 5msec 87msec
Perl 208件 10msec 87msec
InnoDB 225件 31msec 89msec
.ne.jp 464件 9msec 89msec
MySQL 979件 19msec 90msec
bit.ly 1,396件 27msec 93msec
[space]@ 2,689件 57msec 105msec
RT 3,028件 61msec 104msec
http:// 4,227件 64msec 103msec

このように、今回試した検索語においてはすべて転置インデックスを使った方が速いという結果が出ました。「http://」の場合は全体の12%もヒットしてしまうのですが、それでもLIKE検索でフルスキャンを行うよりは速いようです。グラフにすると傾向が見えてきます。

もう少し工夫すれば、使いどころが出てくるかもしれませんね。