Neo Inspiration

Avatar

中の人:jakkrokk (HN統一しました) がphp,javascript,air,flex などなど好き勝手書いてます。仕事は主にWEBシステムの設計と開発、SEO全般など。

mysql4.0系でfulltextを利用したマルチバイト全文検索2

前回の続き

とりあえず強引に mysql4.0系でマルチバイト対応全文検索してみるだけ。
簡単に言えばトークンに切り出して、それをmd5して別テーブルに格納するという力ワザ。

どうやってトークンを切りだすか

以前流行った正規表現ベースのトークンとn-gramで速度比較

100文字ほどのデータだと
%php -f morph.php  ←正規表現ベース
0.0232629776001sec
%
%php -f ngram.php ←tri-gram
0.029877967834sec
%

10万文字ほどのデータだと
%php -f morph.php ←正規表現ベース
8.76749396324sec%
%
%php -f ngram.php ←tri-gram
9.08151698112sec%

あんまりかわらないのでワンライナーな正規表現ベースでやってみる。

こんなかんじの正規表現。

$r = preg_match_all('/[一-龠々〆ヵヶ]+|[ぁ-ん]+|[ァ-ヴ]+|[a-zA-Z0-9]+|[a-zA-Z0-9]+/u', $str, $m);
 

この正規表現で各レコードの本文を割って
出てきたトークンを一つづつハッシュにしてjoinする

$tempToken = array();
foreach ($m as $v) {
    $hashedToken[] = md5($v);
}
$joinedToken = join(' ',$hashedToken);
 

でこれをトークン専用カラムを作成してそこに突っ込む。

ここまでできたら
検索クエリも上の正規表現でトークンにしてそれをmd5にしてから検索。

テストしてみる
全文中から 86deb27a32903da70a7b2348fcf36bc3(あいうえおのmd5) を検索

SELECT id,MATCH (test_fulltext) AGAINST ('86deb27a32903da70a7b2348fcf36bc3')
FROM **table
WHERE MATCH (test_fulltext) AGAINST ('86deb27a32903da70a7b2348fcf36bc3')
ORDER BY MATCH (test_fulltext) AGAINST ('86deb27a32903da70a7b2348fcf36bc3') ASC

クエリの実行時間 0.0065 秒
 

おーあたったあたった。
しかもこれなら mysqld のft_min_word_lenを4からいじれないっていう件も
強制32文字になるわけだから解決!

ちょいと手間だし、トークンの数 * 32バイト分1レコードのデータが膨れ上がりますが
(まあ全文字md5にしないで、マルチバイトのとこだけ切り出してるわけだから、
そこだけエンコードしてしまえば結構減るかも)
こういう方法で一応できるということで。

っていうか pack() とか使えばもっと短くなるかな。

mysql4.0系でfulltextを利用したマルチバイト全文検索1

とりあえずさくらのサーバに
全文検索も実装しようかなーと思ってsennaとか調べてたんですが、
色々と制約にヒットしてめんどくさくなったんで、
既存の範囲で全文検索ができないかと考えてメモ

ここらへんを参考に
MySQL 全文検索
MySQL FULLTEXT + Ngram : LIKE検索より数十倍高速な、お手軽 日本語全文検索 について

mysql の fulltext型のインデックスを使って実装するのが一般的らしい。
で このfulltext型ってのが何をやってるかといえば

MySQL では、非常に単純なパーサを使用してテキストをワード(語)に分割します。“ワード” とは、文字、数字、‘’’、‘_’ で構成される文字列です。

だそうで。

ただ、

MySQL 4.1.1 以降はマルチバイト文字も対応。
ただし Unicode は utf8 のみ対応 (ucs2はNG)。

えーっと さくらのDBは 4.0.27なわけですが・・・

さらに
4.1だと文字揺らぎの修正(半角<>全角とか)
+QUERY EXPANSION MODE という付加モードも使用可能。。

ということで、あきらかに4.1を使ったほうがいいんですが、
4.0でも最低限動かせないかなということで試行錯誤開始。

とりあえずやってみる

とりあえずそこらへんは無視して
既存にあるテーブル(レコード数6000)の
コンテンツテーブル(text型、1つのレコードにつきマルチバイトで1000文字くらい)のものでやってみる。
(DBはUTF-8統一)

ALTER TABLE **table ADD FULLTEXT(contents);

6000行もあるから結構かかるのかとおもったら5秒ほどで終了。
え これほんとにできてんのかな・・

とりあえずテスト

全文中から Screenshot っていう言葉を検索

SELECT id,MATCH (contents) AGAINST ('Screenshot')
FROM **table
WHERE MATCH (contents) AGAINST ('Screenshot')
ORDER BY MATCH (contents) AGAINST ('Screenshot') ASC

クエリの実行時間 0.0054 秒

おお早い早い。
ちゃんと結果も100件ほど出てきた。

LIKEにしてみる

SELECT *
FROM **table
WHERE contents LIKE '%Screenshot%'

クエリの実行時間 0.2419 秒

やっぱこっちは実用的じゃない。
しかも結果は160件ほど。

じゃあ日本語はどうかというと。。

全文中から ミュージック っていう言葉を検索

SELECT id,MATCH (contents) AGAINST ('ミュージック')
FROM **table
WHERE MATCH (contents) AGAINST ('ミュージック')
ORDER BY MATCH (contents) AGAINST ('ミュージック') ASC

クエリの実行時間 0.0057 秒

検索結果0。手動で入れてみたんだけどやっぱり0。
やっぱ機能していない。
でもまあ今更UTF8からEUCにDB変えるとかナンセンスすぎるので
どうしようかなーと。

やり方を考える

日本語トークンを切り出してハッシュにでもする?

できそうな気がしないでもないので、やってみる。

続き

XML DB

10万くらいのRSSをうまく管理するのはどうすっかなーと
新しいシステムについてつらつら考えていたのだけれど、
ふとXMLベースのDBってどうなんだろうと思い出して調査開始。

キャッシュがきかない、トランザクションがきかない
とかI/O周りの負荷が高すぎて
結局具合のいいパーサかいてやるか
一旦mysqlに格納しちまったほうがよさそうだなー

と思いつつとりあえず使ってみた。

Xprioriってのでテスト
Xpriori

ここらへんを参考にXquery(SQLの代わりに投げるDB用クエリ)をいじってみる
Xquery

・・・
スキーマレスDBっていうのは
RSSのようにスキーマがガンガン変わるようなものを
一括で管理するにはいいとおもったんだけど、
どうも勝手がちがったw

JOINとかは1ファイル1テーブルで、JOINはファイルを結合してくイメージだから・・
あかんw10万ファイルをJOIN(実質MAX1000くらいなんだろうけど)
とか気が遠くなるこれw

10万ファイルをDBに格納してRSSを生成しなおすのと(こっちは格納コストが高い)
ファイルとして保持して、Xqueryで生成するのと・・(こっちは生成コストが高い)
うーん ちょっと簡単にはコスト計算できそうもないので、要検証。
パーサ書くほうが楽しいしとりあえずやめよっかなーとか。

余談だけどXqueryに関するこの記事をよんで
めっちゃ面白そうだとはおもった
http://d.hatena.ne.jp/stemy/20060708/1152385620

Continue Previous page Next page

Twitter