* MySQLバインディング

* Senna MySQLバインディングとは?

MySQLは、バージョン3.23.23以降、FULLTEXTインデックスをサポートしており、
VARCHAR, TEXT型のフィールドに対して全文検索を実行することができますが、
1) 日本語のサポートが不完全、2) フレーズ検索が遅い、3) 更新が遅い、
といった問題点があります。

Senna MySQLバインディングは、MySQLのFULLTEXTインデックスを置き換える形で
Sennaライブラリを組み込むもので、上記の問題点を全て解消し、
高速・高精度なSennaの特徴をMySQLからフルに使用可能にします。

また全文検索に関する構文は、MySQLのオリジナルのFULLTEXTインデックス向けの
構文がそのまま使用可能なため、スムーズに適用することができます。

* 使い方

Senna MySQLバインディングを使用するためには、Senna組み込み版のMySQLをインストールします。
Senna組み込み版のMySQLをインストールするには二つの方法があります。

+ Senna MySQLバインディングを組み込み済みのバイナリパッケージをインストールする
+ MySQLのソースにSenna MySQLバインディングパッチを当ててコンパイル・インストールする

インストール後、MySQLサーバを起動すれば、
http://dev.mysql.com/doc/refman/4.1/ja/fulltext-search.html
に書かれている方法でSennaのfulltext indexを構築し、
全文検索機能を使用することができるようになります。

* 機能と制限事項

- 4.0系, 4.1系, 5.0系, 5.1系の各最新バージョンのMySQLで使用できます。
- IN BOOLEAN MODEとデフォルト(NLQ MODE)の全文検索(MATCH AGAINST)クエリをサポートします。
- IN BOOLEAN MODEでは、+, -, <, >, (, ), ~, *, " の全演算子が使用できます。
- EUC, SJIS, UTF8の3種類のエンコーディングを使用できます。
- 文字正規化機能を使用できます。UTF8の場合はNFKC正規化をサポートします。
- 関連文書検索機能が使用できます。
- 近傍(近接)検索機能が使用できます。
- MyISAM と MERGEの2種類のストレージエンジンをサポートします。(MERGEは4.0.27のみ)
- スニペット機能が使用できます。
- 単語インデックス(mecab使用), NGRAMインデックス, 空白区切りインデックスの3種類が選択できます。
- fulltextインデックスと他のインデックスを組み合わせて検索処理を高速化する機能があります(2ind patch)
- ヒット件数取得とLIMIT指定検索を高速化する機能があります(skipmode patch) (4.0.27のみ)

* インストール手順詳細

[[install]]を参照下さい。

* 独自拡張機能

Senna MySQLバインディングでは、より実用的な全文検索アプリケーションで
MySQLを使用できるようにいくつかの拡張機能を加えています。
(拡張機能を提供してくださったGREEの小泉さん、iYappoの大沢さんに深く感謝します)

** CREATE TABLE / CREATE INDEX 句の拡張

生成する Senna の各種パラメータを指定できます。

*** インデックスタイプの指定

単語インデックス, NGRAMインデックス, DELIMITEDインデックスの3種のいずれかを選択できます。

**** 単語(mecab)インデックスを作成する場合

インデックスタイプを指定しなければ単語インデックスが使用されます。

(例)

   CREATE TABLE test (
     id INTEGER AUTO_INCREMENT,
     PRIMARY KEY (id),
     text TEXT NOT NULL,
     FULLTEXT INDEX (text)
   );

**** NGRAMインデックスを作成する場合

USING NGRAMを指定します。

(例)

   CREATE TABLE test (
     id INTEGER AUTO_INCREMENT,
     PRIMARY KEY (id),
     text TEXT NOT NULL,
     FULLTEXT INDEX USING NGRAM (text)
   );

**** DELIMITEDインデックスを作成する場合

USING DELIMITEDを指定すると、空白で区切られた文字列単位でインデックスを作成します。

(例)

   CREATE TABLE test (
     id INTEGER AUTO_INCREMENT,
     PRIMARY KEY (id),
     text TEXT NOT NULL,
     FULLTEXT INDEX USING DELIMITED (text)
   );

**** MySQLの素のfulltext indexを作成する(sennaインデックスを使用しない)場合

USING NO SENNAを指定します。

(例)

   CREATE TABLE test (
     id INTEGER AUTO_INCREMENT,
     PRIMARY KEY (id),
     text TEXT NOT NULL,
     FULLTEXT INDEX USING NO SENNA (text)
   );

**** 正規化機能の使用/不使用

デフォルトでは正規化機能が有効になります。
無効化するためには、USING NO NORMALIZEを指定します。

(例) 正規化なしでNGRAMインデックスを作成する場合

   CREATE TABLE test (
     id INTEGER AUTO_INCREMENT,
     PRIMARY KEY (id),
     text TEXT NOT NULL,
     FULLTEXT INDEX USING SENNA, NO NORMALIZE, NGRAM (text)
   );

**** INITIAL_N_SEGMENTS指定

USING 数値を指定することによって、INITIAL_N_SEGMENTS
(インデックスバッファ領域の初期値)をインデックス作成時に設定できます。

(例) INITIAL_N_SEGMENTS=2048でインデックスを作成する場合

   CREATE TABLE test (
     id INTEGER AUTO_INCREMENT,
     PRIMARY KEY (id),
     text TEXT NOT NULL,
     FULLTEXT INDEX USING SENNA, 2048 (text)
   );

** SHOW INDEX 句の拡張

CREATE TABLE / CREATE INDEX の拡張に伴い SHOW INDEX 句も拡張して、使用しているインデックスの種類を調べることができるようになりました。

  mysql> SHOW INDEX FROM test;
  +-------+------------+----------+- ... -+--------+------+--------------------------------+---------+
  | Table | Non_unique | Key_name |       | Packed | Null | Index_type                     | Comment |
  +-------+------------+----------+- ... -+--------+------+--------------------------------+---------+
  | test  |          0 | PRIMARY  |       | NULL   |      | BTREE                          |         |
  | test  |          1 | text     |       | NULL   | YES  | FULLTEXT,SENNA,NORMALIZE,NGRAM |         |
  +-------+------------+----------+--... -+--------+------+--------------------------------+---------+ 
  2 rows in set (0.04 sec)

** SHOW TABLE STATUS 句の拡張

SHOW TABLE STATUS 句 によって、あるテーブルで使われている Senna の転置テーブルや語彙テーブルのレコード数の合計や、インデックスファイルのサイズを知ることができるようになります。複数の全文検索インデックスがあったときにはあまり意味がありませんが...。

  +-------+--------+ ... -------+----------------+----------------+--------------------+---------+
  | Name  | Type   | ... k_time | Create_options | Senna_key_size | Senna_lexicon_size | Comment | 
  +-------+--------+ ... -------+----------------+----------------+--------------------+---------+
  | test1 | MyISAM | ...        |                |              0 |                  0 |         |
  | test2 | MyISAM | ...        |                |              0 |                  0 |         |
  | test3 | MyISAM | ...        |                |              0 |                  0 |         |
  | test4 | MyISAM | ...        |                |              0 |                  0 |         |
  | test5 | MyISAM | ...        |                |              0 |                  0 |         |
  +-------+--------+ ... -------+----------------+----------------+--------------------+---------+

次のカラムが追加されています。

- Senna_keys_size

シンボル表 (.SEN) のレコード数の合計を示します。

- Senna_keys_file_size

シンボル表 (.SEN) のファイルサイズの合計を示します。

- Senna_lexicon_size

語彙テーブル (.SEN.I) のレコード数の合計を示します。

- Senna_lexicon_file_size

語彙テーブル (.SEN.I) のファイルサイズの合計を示します。

- Senna_inv_seg_size

転置テーブルのバッファ (.SEN.i) のファイルサイズを示します。

- Senna_inv_chunk_size

転置テーブル (.SEN.i.c) のファイルサイズを示します。

** 2ind patch(2インデックス同時使用機能)

*** 2ind patchとは

MySQLでクエリを実行する際には、
１つのテーブルに対して１つのインデックスしか利用できません。

通常のクエリの場合は、
複数のカラムにまたがったインデックスを用意することによって、
現実的にこの制限を回避することができます。

しかし、全文検索用のインデックスを用いた場合、
他のインデックスが利用できないため、
以下のような問題が生じます。

1. limit指定で出力を制限しても応答が遅い問題

   select columns from table where match(a) against(b) limit 1000, 10

のように、オフセットに大きな値を指定するとテーブルスキャンが発生し、
応答が遅くなります。

2. count(*)等で件数を取得するだけでも応答が遅い問題

   select count(*) from table where match(a) against(b);

のように件数を取得するだけでもテーブルスキャンが発生し、
応答が遅くなります。

3. 全文検索以外の条件で絞り込む処理が遅い問題

   select columns from table where match(a) against(b) and c like 'hoge%';

のように、全文検索以外に他のカラムに関する条件を指定した場合、
そのカラムにインデックスが張られていてもテーブルスキャンが発生し、
応答が遅くなります。

4. 全文検索以外の条件でソートする処理が遅い問題

   select columns from table where match(a) against(b) order by c;

のように、ソート条件を指定した場合、そのカラムにインデックスが
張られていてもテーブルスキャンが発生し、応答が遅くなります。。

このような問題を解決するために、
MySQLが全文検索用のインデックスと通常のインデックスの両方を併用できるようにする2ind-patchパッチが使用できます。

*** 2ind patchのインストール(ソースからインストールする場合)

MySQLソースに、mysql-x.x.x.senna.diff を当てたあと、

 patch -p1 < {senna-package}/bindings/mysql/mysql-x.x.x.senna.2ind.diff

のように実行して、2ind patchを当てます。その他の手順は[[install]]の通りです。

*** 2ind-patchの使い方
- 1. 2.のパタンについては、特に意識することなく、通常通りにSQLを発行するだけで2ind-patchの効果が得られます。
- 3. 4.のパタンについては、絞り込みやソート時に使用したいインデックスを以下のように明示的に指定する必要があります。
   select columns from table force index(c) where match(a) against(b) and c like 'hoge%';

   select columns from table force index(c) where match(a) against(b) order by c;

(主キーであれば force index(PRIMARY) のように指定します)

*** 2ind-patchの注意点

-前述の4つのクエリパタン以外では効果が得られるとは限りません。全文検索条件で大量のレコードがヒットすることによって発生するディスクI/Oが性能阻害要因である場合にのみ効果が期待できます。
-本パッチはまだβ版です。MySQLの安定性に及ぼす影響等は未確認です。

** snippet UDF

検索結果を表示する際に、文書中のキーワードの周辺のみを表示したい場合があります。
このキーワードの周辺のテキストを、snippetまたはKWIC(KeyWord In Context)と呼びます。
この機能を実現するのが、snippet UDFです。

***snippet UDFのインストール

UDFのディレクトリに移動
 > cd {senna-package}/bindings/mysql/udf/
''Subversionからソースを取得した場合のみ、''autogen.shの実行
 > ./autogen.sh
configure
 > ./configure --prefix=/usr
makeします。
 > make
installします。
 > sudo make install
MySQLにUDFを組み込みます。
ちなみに、make loadでは「mysql -u root -p」コマンドを呼び出してUDFを登録しています。
MySQLのrootユーザのパスワードを入力ください。
 > make load

*** snippet UDFの使い方
snippet UDFは大変引数の多いMySQLの関数です。

 SELECT snippet(文書, snippetの長さの最大バイト数, snippetの最大個数, 文書の文字コード, 
   htmlエンコーディングの有無, snippetの開始タグ, snippetの終了タグ, 
   単語1, 単語1の前につけられるタグ, 単語1の後につけられるタグ, 
   単語2, 単語2の前につけられるタグ, 単語2の後につけられるタグ, ...);

文字コードは、sjis, ujis, utf8, defaultのいずれかを指定ください。
htmlエンコーディングの有無は、0か1を指定ください。
1の場合は文書中の<,>,&,"をそれぞれ&lt;,&gt;,&amp;,&quot;に変換して出力します。

例は以下のとおりです。

 SELECT snippet(body, 120, 3, 'ujis', 
   1, '...', '...<br>\n', 
   '検索', '<span class="word1">', '</span>', 
   '実験', '<span class="word2">', '</span>'
 )
 FROM contents
 WHERE MATCH(body) AGAINST('*D+ 検索 実験' IN BOOLEAN MODE)
 LIMIT 0,10;

** skipmode-patch

skipmode-patchは、検索クエリーにおいてヒット件数取得とLIMIT指定検索を高速化する機能です。
MySQL-4.0.27でのみ使用することができます。

*** skipmode patchのインストール

MySQLソースに、mysql-x.x.x.senna.diff を当てたあと、

 patch -p1 < {senna-package}/bindings/mysql/mysql-x.x.x.senna.2ind.diff
 patch -p1 < {senna-package}/bindings/mysql/mysql-x.x.x.senna.2ind.skipmode.diff

のように実行して、2ind patchとskipmode patchの両方を当てます。
その他の手順は[[install]]の通りです。
