4.3. ストレージモード¶
ここでは Mroonga におけるストレージモードの利用方法を説明します。
4.3.1. 全文検索の利用方法¶
インストールが確認できたら、テーブルを1つ作成してみましょう。 ENGINE = Mroonga
とMroongaを指定するところがポイントです。:
mysql> CREATE TABLE diaries (
-> id INT PRIMARY KEY AUTO_INCREMENT,
-> content VARCHAR(255),
-> FULLTEXT INDEX (content)
-> ) ENGINE = Mroonga DEFAULT CHARSET utf8;
Query OK, 0 rows affected (0.10 sec)
INSERTでデータを投入してみましょう。
mysql> INSERT INTO diaries (content) VALUES ("It'll be fine tomorrow.");
Query OK, 1 row affected (0.01 sec)
mysql> INSERT INTO diaries (content) VALUES ("It'll rain tomorrow");
Query OK, 1 row affected (0.00 sec)
全文検索を実行してみます。
mysql> SELECT * FROM diaries WHERE MATCH(content) AGAINST("fine");
+----+-----------------------------------------+
| id | content |
+----+-----------------------------------------+
| 1 | It'll be fine tomorrow. |
+----+-----------------------------------------+
1 row in set (0.00 sec)
おぉぉー。検索できましたね。
4.3.2. 検索スコアの取得方法¶
注釈
1.0.0以前のMroongaではMySQLの標準的な検索スコアの取得方法ではなく、 _score
という専用のカラムを作成するという独自の方法でした。1.0.0からはMySQLの標準的な取得方法になっています。
全文検索を行う際、指定したキーワードにより内容が一致するレコードを上位に表示したいというような場合があります。そうしたケースでは検索スコアを利用します。
検索スコアはMySQLの標準的な方法 [1] で取得できます。つまり、SELECTの取得するカラム名を指定するところやORDER BYのところにMATCH...AGAINSTを指定します。
それでは実際にやってみましょう。:
mysql> INSERT INTO diaries (content) VALUES ("It's fine today. It'll be fine tomorrow as well.");
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO diaries (content) VALUES ("It's fine today. But it'll rain tomorrow.");
Query OK, 1 row affected (0.00 sec)
mysql> SELECT *, MATCH (content) AGAINST ("fine") FROM diaries WHERE MATCH (content) AGAINST ("fine") ORDER BY MATCH (content) AGAINST ("fine") DESC;
+----+--------------------------------------------------------------+------------------------------------+
| id | content | MATCH (content) AGAINST ("fine") |
+----+--------------------------------------------------------------+------------------------------------+
| 3 | It's fine today. It'll be fine tomorrow as well. | 2 |
| 1 | It'll be fine tomorrow. | 1 |
| 4 | It's fine today. But it'll rain tomorrow. | 1 |
+----+--------------------------------------------------------------+------------------------------------+
3 rows in set (0.00 sec)
検索対象の文字列 晴れ
をより多く含む、すなわち検索スコアの高い id = 3
のメッセージが上に来ていることが確認できます。また、SELECT句にMATCH AGAINSTを記述しているため、検索スコアも取得できています。
属性名を変更したい場合は AS
を使って下さい。
mysql> SELECT *, MATCH (content) AGAINST ("fine") AS score FROM diaries WHERE MATCH (content) AGAINST ("fine") ORDER BY MATCH (content) AGAINST ("fine") DESC;
+----+--------------------------------------------------------------+-------+
| id | content | score |
+----+--------------------------------------------------------------+-------+
| 3 | It's fine today. It'll be fine tomorrow as well. | 2 |
| 1 | It'll be fine tomorrow. | 1 |
| 4 | It's fine today. But it'll rain tomorrow. | 1 |
+----+--------------------------------------------------------------+-------+
3 rows in set (0.00 sec)
4.3.3. 全文検索用パーサの変更¶
MySQLは全文検索用のパーサ [2] を指定する以下のような構文を持っています。:
FULLTEXT INDEX (content) WITH PARSER parser_name
しかし、この構文を利用する場合は、あらかじめすべてのパーサをMySQLに登録しておく必要があります。一方、Groongaはトークナイザー(MySQLでいうパーサ)を動的に追加することができます。そのため、Mroongaでもこの構文を採用するとGroonga側に動的に追加されたトークナイザーに対応できなくなります。Groongaに動的に追加されるトークナイザーにはMeCabを用いたトークナイザーもあり、この制限に縛られることは利便性を損なうと判断し、以下のようなコメントを用いた独自の構文を採用することにしました。:
FULLTEXT INDEX (content) COMMENT 'parser "TokenMecab"'
注釈
FULLTEXT INDEX
に COMMENT
を指定できるのはMySQL 5.5からになります。MySQL 5.1を利用している場合は後述の mroonga_default_parser
変数を利用してください。
パーサに指定できるのは以下の値です。
- off
トークナイズしません。"off"は``content``をそのまま扱いたい場合に使います。例えば、この値は前方一致検索のために指定します。
- TokenBigram
バイグラムでトークナイズする。ただし、連続したアルファベット・連続した数字・連続した記号はそれぞれ1つのトークンとして扱う。そのため、3文字以上のトークンも存在する。これはノイズを減らすためである。
デフォルト値。
- TokenMecab
MeCabを用いてトークナイズする。groongaがMeCabサポート付きでビルドされている必要がある。
- TokenBigramSplitSymbol
バイグラムでトークナイズする。TokenBigramと異なり、記号が連続していても特別扱いして1つのトークンとして扱わず通常のバイグラムの処理を行う。
TokenBigramではなくTokenBigramSplitSymbolを利用すると「Is it really!?!?!?」の「!?!?!?」の部分に「!?」でマッチする。TokenBigramの場合は「!?!?!?」でないとマッチしない。
- TokenBigramSplitSymbolAlpha
バイグラムでトークナイズする。TokenBigramSplitSymbolに加えて、連続したアルファベットも特別扱いせずに通常のバイグラムの処理を行う。
TokenBigramではなくTokenBigramSplitSymbolAlphaを利用すると「Is it really?」に「real」でマッチする。TokenBigramの場合は「really」でないとマッチしない。
- TokenBigramSplitSymbolAlphaDigit
バイグラムでトークナイズする。TokenBigramSplitSymbolAlphaに加えて、連続した数字も特別扱いせずに通常のバイグラムの処理を行う。つまり、すべての字種を特別扱いせずにバイグラムの処理を行う。
TokenBigramではなくTokenBigramSplitSymbolAlphaDigitを利用すると「090-0123-4567」に「567」でマッチする。TokenBigramの場合は「4567」でないとマッチしない。
- TokenBigramIgnoreBlank
バイグラムでトークナイズする。TokenBigramと異なり、空白を無視して処理する。
TokenBigramではなくTokenBigramIgnoreBlankを利用すると「み な さ ん 注 目」に「みなさん」でマッチする。TokenBigramの場合は「み な さ ん」でないとマッチしない。
- TokenBigramIgnoreBlankSplitSymbol
バイグラムでトークナイズする。TokenBigramSplitSymbolと異なり、空白を無視して処理する。
TokenBigramSplitSymbolではなくTokenBigramIgnoreBlankSplitSymbolを利用すると「! !? ??」に「???」でマッチする。TokenBigramSplitSymbolの場合は「? ??」でないとマッチしない。
- TokenBigramIgnoreBlankSplitSymbolAlpha
バイグラムでトークナイズする。TokenBigramSplitSymbolAlphaと異なり、空白を無視して処理する。
TokenBigramSplitSymbolAlphaではなくTokenBigramIgnoreBlankSplitSymbolAlphaを利用すると「I am a pen.」に「ama」でマッチする。TokenBigramSplitSymbolAlphaの場合は「am a」でないとマッチしない。
- TokenBigramIgnoreBlankSplitSymbolAlphaDigit
バイグラムでトークナイズする。TokenBigramSplitSymbolAlphaDigitと異なり、空白を無視して処理する。
okenBigramSplitSymbolAlphaDigitではなくTokenBigramIgnoreBlankSplitSymbolAlphaDigitを利用すると「090 0123 4567」に「9001」でマッチする。TokenBigramSplitSymbolAlphaDigitの場合は「90 01」でないとマッチしない。
- TokenDelimit
空白区切りでトークナイズする。
「movie horror topic」をトークナイズすると「movie」、「horror」、「topic」になります。
- TokenDelimitNull
null文字(\0)区切りでトークナイズする。
「movie\0horror\0topic」をトークナイズすると「movie」、「horror」、「topic」になります。
- TokenUnigram
ユニグラムでトークナイズする。ただし、連続したアルファベット・連続した数字・連続した記号はそれぞれ1つのトークンとして扱う。そのため、2文字以上のトークンも存在する。これはノイズを減らすためである。
- TokenTrigram
トリグラムでトークナイズする。ただし、連続したアルファベット・連続した数字・連続した記号はそれぞれ1つのトークンとして扱う。そのため、4文字以上のトークンも存在する。これはノイズを減らすためである。
デフォルトのパーサは configure
の --with-default-parser
オプションでビルド時に指定することができます。:
./configure --with-default-parser TokenMecab ...
また、my.cnfまたはSQL内で mroonga_default_parser
変数を指定することでも指定できます。my.cnfで指定するとMySQLを再起動しても値は変更されたままですが、反映させるために再起動しなければいけません。一方、SQLで指定した場合はすぐに設定が反映されますが、MySQLが再起動すると設定は失われます。
my.cnf:
[mysqld]
mroonga_default_parser=TokenMecab
SQL:
mysql> SET GLOBAL mroonga_default_parser = TokenMecab;
Query OK, 0 rows affected (0.00 sec)
4.3.4. ノーマライザーの指定方法¶
Mroongaは、文書のエンコーディング(照合順序)に応じたノーマライザーを使用します。これはテキストをトークナイズするときとテーブルのキーを保存するときに使われます。
utf8_general_ci
または utf8mb4_general_ci
の場合、NormalizerMySQLGeneralCI
ノーマライザーが使用されます。
utf8_unicode_ci
または utf8mb4_unicode_ci
の場合、NormalizerMySQLUnicodeCI
ノーマライザーが使用されます。
utf8_bin
の場合、ノーマライザーは使用されません。
以下は、 utf8_unicode_ci
の照合順序を指定して NormalizerMySQLUnicodeCI
ノーマライザーを使用する例です。:
mysql> SET NAMES utf8;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE diaries (
-> day DATE PRIMARY KEY,
-> content VARCHAR(64) NOT NULL,
-> FULLTEXT INDEX (content)
-> ) Engine=Mroonga DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Query OK, 0 rows affected (0.18 sec)
mysql> INSERT INTO diaries VALUES ("2013-04-23", "ブラックコーヒーを飲んだ。");
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM diaries
-> WHERE MATCH (content) AGAINST ("+ふらつく" IN BOOLEAN MODE);
+------------+-----------------------------------------+
| day | content |
+------------+-----------------------------------------+
| 2013-04-23 | ブラックコーヒーを飲んだ。 |
+------------+-----------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM diaries
-> WHERE MATCH (content) AGAINST ("+ブラック" IN BOOLEAN MODE);
+------------+-----------------------------------------+
| day | content |
+------------+-----------------------------------------+
| 2013-04-23 | ブラックコーヒーを飲んだ。 |
+------------+-----------------------------------------+
1 row in set (0.00 sec)
Mroongaは、Groongaのノーマライザーを指定する以下のような構文を持っています。:
FULLTEXT INDEX (content) COMMENT 'normalizer "NormalizerAuto"'
Groongaのノーマライザーの詳細については Groongaのドキュメント を参照してください。
以下は、NormalizerAuto
のノーマライザーを使用する例です。:
mysql> SET NAMES utf8;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE diaries (
-> day DATE PRIMARY KEY,
-> content VARCHAR(64) NOT NULL,
-> FULLTEXT INDEX (content) COMMENT 'normalizer "NormalizerAuto"'
-> ) Engine=Mroonga DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Query OK, 0 rows affected (0.19 sec)
mysql> INSERT INTO diaries VALUES ("2013-04-23", "ブラックコーヒーを飲んだ。");
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM diaries
-> WHERE MATCH (content) AGAINST ("+ふらつく" IN BOOLEAN MODE);
Empty set (0.00 sec)
mysql> SELECT * FROM diaries
-> WHERE MATCH (content) AGAINST ("+ブラック" IN BOOLEAN MODE);
+------------+-----------------------------------------+
| day | content |
+------------+-----------------------------------------+
| 2013-04-23 | ブラックコーヒーを飲んだ。 |
+------------+-----------------------------------------+
1 row in set (0.00 sec)
4.3.5. トークンフィルターの指定方法¶
Mroongaは、Groongaのトークンフィルターを指定する以下のような構文を持っています。:
FULLTEXT INDEX (content) COMMENT 'token_filters "TokenFilterStem"'
以下は、TokenFilterStem
のトークンフィルターを使用する例です。:
mysql> SELECT mroonga_command('register token_filters/stem');
+------------------------------------------------+
| mroonga_command('register token_filters/stem') |
+------------------------------------------------+
| true |
+------------------------------------------------+
1 row in set (0.00 sec)
mysql> CREATE TABLE memos (
-> id INT NOT NULL PRIMARY KEY,
-> content TEXT NOT NULL,
-> FULLTEXT INDEX (content) COMMENT 'normalizer "NormalizerAuto", token_filters "TokenFilterStem"'
-> ) Engine=Mroonga DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.18 sec)
mysql> INSERT INTO memos VALUES (1, "I develop Groonga");
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO memos VALUES (2, "I'm developing Groonga");
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO memos VALUES (3, "I developed Groonga");
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM memos
-> WHERE MATCH (content) AGAINST ("+develops" IN BOOLEAN MODE);
+----+------------------------+
| id | content |
+----+------------------------+
| 1 | I develop Groonga |
| 2 | I'm developing Groonga |
| 3 | I developed Groonga |
+----+------------------------+
3 rows in set (0.01 sec)
Groongaのトークンフィルターの詳細については Groongaのドキュメント を参照してください。
以下は、TokenFilterStopWord
のトークンフィルターを使用する例です。:
mysql> SELECT mroonga_command("register token_filters/stop_word");
+-----------------------------------------------------+
| mroonga_command("register token_filters/stop_word") |
+-----------------------------------------------------+
| true |
+-----------------------------------------------------+
1 row in set (0.00 sec)
mysql> CREATE TABLE terms (
-> term VARCHAR(64) NOT NULL PRIMARY KEY,
-> is_stop_word BOOL NOT NULL
-> ) Engine=Mroonga COMMENT='default_tokenizer "TokenBigram", token_filters "TokenFilterStopWord"' DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.12 sec)
mysql> CREATE TABLE memos (
-> id INT NOT NULL PRIMARY KEY,
-> content TEXT NOT NULL,
-> FULLTEXT INDEX (content) COMMENT 'table "terms"'
-> ) Engine=Mroonga DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.17 sec)
mysql>
mysql> INSERT INTO terms VALUES ("and", true);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO memos VALUES (1, "Hello");
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO memos VALUES (2, "Hello and Good-bye");
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO memos VALUES (3, "Good-bye");
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM memos
-> WHERE MATCH (content) AGAINST ("+\"Hello and\"" IN BOOLEAN MODE);
+----+--------------------+
| id | content |
+----+--------------------+
| 1 | Hello |
| 2 | Hello and Good-bye |
+----+--------------------+
2 rows in set (0.01 sec)
これは、全文検索用の語彙表テーブルを指定する方法を使用しています。
4.3.6. Groongaのカラムフラグの指定方法¶
Mroongaは、Groongaのカラムフラグを指定する以下のような構文を持っています。:
content TEXT COMMENT 'flags "COLUMN_SCALAR|COMPRESS_ZLIB"'
以下は、COMPRESS_ZLIB
フラグを使用する例です。:
mysql> CREATE TABLE entries (
-> id INT UNSIGNED PRIMARY KEY,
-> content TEXT COMMENT 'flags "COLUMN_SCALAR|COMPRESS_ZLIB"'
-> ) Engine=Mroonga DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.12 sec)
Groongaのカラムフラグの詳細については Groongaのドキュメント を参照してください。
4.3.7. 位置情報検索の利用方法¶
ストレージモードでは全文検索だけではなく位置情報検索も高速に実行できます。ただし、MyISAMとは異なりデータとして格納できるのはPOINT型のみです。LINEなどの他のデータ型は保存できません。また、インデックスを用いた高速な検索に対応しているのはMBRContainsだけです。MBRDisjointなどには対応していません。
位置情報検索を利用する場合のテーブル定義はMyISAMと同様にPOINT型のカラムを定義し、そのカラムに対してSPATIAL INDEXを指定します。:
mysql> CREATE TABLE shops (
-> id INT PRIMARY KEY AUTO_INCREMENT,
-> name VARCHAR(255),
-> location POINT NOT NULL,
-> SPATIAL INDEX (location)
-> ) ENGINE = Mroonga;
Query OK, 0 rows affected (0.06 sec)
データの登録方法もMyISAMのときと同様にGeomFromText()関数を使って文字列からPOINT型の値を作成します。:
mysql> INSERT INTO shops VALUES (null, 'Nezu\'s Taiyaki', GeomFromText('POINT(139.762573 35.720253)'));
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO shops VALUES (null, 'Naniwaya', GeomFromText('POINT(139.796234 35.730061)'));
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO shops VALUES (null, 'Yanagiya Taiyaki', GeomFromText('POINT(139.783981 35.685341)'));
Query OK, 1 row affected (0.00 sec)
池袋駅(139.7101 35.7292)が左上の点、東京駅(139.7662 35.6815)が右下の点となるような長方形内にあるお店を探す場合は以下のようなSELECTになります。:
mysql> SELECT id, name, AsText(location) FROM shops WHERE MBRContains(GeomFromText('LineString(139.7101 35.7292, 139.7662 35.6815)'), location);
+----+-----------------------+------------------------------------------+
| id | name | AsText(location) |
+----+-----------------------+------------------------------------------+
| 1 | Nezu's Taiyaki | POINT(139.762572777778 35.7202527777778) |
+----+-----------------------+------------------------------------------+
1 row in set (0.00 sec)
位置情報で検索できていますね!
4.3.8. レコードIDの取得方法¶
Groongaではテーブルにレコードを追加した際にレコードを一意に識別するための番号が割当てられます。
Mroongaではアプリケーションの開発を容易にするため、このレコードIDをSQLで取得できるようになっています。
レコードIDを取得するためには、テーブル定義時に _id
という名前のカラムを作成して下さい。
mysql> CREATE TABLE memos (
-> _id INT,
> content VARCHAR(255),
-> UNIQUE KEY (_id) USING HASH
-> ) ENGINE = Mroonga;
Query OK, 0 rows affected (0.04 sec)
_idカラムのデータ型は整数型(TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT)である必要があります。
また_idカラムにはインデックスを作成することが可能ですが、HASH形式である必要があります。
INSERTでテーブルにレコードを追加してみましょう。_idカラムは仮想カラムとして実装されており、また_idの値であるレコードIDはGroongaにより割当てられるため、SQLによる更新時に値を指定することはできません。更新対象から外すか、値に null
を使用する必要があります。
mysql> INSERT INTO memos VALUES (null, "Saury for today's dinner.");
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO memos VALUES (null, "Update mroonga tomorrow.");
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO memos VALUES (null, "Buy some dumpling on the way home.");
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO memos VALUES (null, "Thank God It's meat day.");
Query OK, 1 row affected (0.00 sec)
レコードIDを取得するには、_idカラムを含むようにしてSELECTを行います。
mysql> SELECT * FROM memos;
+------+------------------------------------------+
| _id | content |
+------+------------------------------------------+
| 1 | Saury for today's dinner. |
| 2 | Update mroonga tomorrow. |
| 3 | Buy some dumpling on the way home. |
| 4 | Thank God It's meat day. |
+------+------------------------------------------+
4 rows in set (0.00 sec)
また直前のINSERTにより割当てられたレコードIDについては、last_insert_grn_id関数により取得することもできます。
mysql> INSERT INTO memos VALUES (null, "Just one bottle of milk in the fridge.");
Query OK, 1 row affected (0.00 sec)
mysql> SELECT last_insert_grn_id();
+----------------------+
| last_insert_grn_id() |
+----------------------+
| 5 |
+----------------------+
1 row in set (0.00 sec)
last_insert_grn_id関数はユーザ定義関数(UDF)としてMroongaに含まれていますが、インストール時にCREATE FUNCTIONでMySQLに追加していない場合には、以下の関数定義DDLを実行しておく必要があります。
mysql> CREATE FUNCTION last_insert_grn_id RETURNS INTEGER SONAME 'ha_mroonga.so';
ご覧のように_idカラムやlast_insert_grn_id関数を通じてレコードIDを取得することができました。ここで取得したレコードIDは後続のUPDATEなどのSQL文で利用すると便利です。
mysql> UPDATE memos SET content = "So much milk in the fridge." WHERE _id = last_insert_grn_id();
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
4.3.9. スニペット(キーワード周辺のテキスト)の取得方法¶
Mroonga provides functionality to get keyword in context. It is implemented as 'mroonga_snippet' UDF.
詳細は mroonga_snippet を参照してください。
4.3.10. Groongaコマンドの実行方法¶
ストレージモードではMroongaはすべてのデータをGroongaのデータベースに保存します。Mroongaを使うことでSQLでGroongaのデータベースにアクセスできるようになります。SQLはとても強力ですが、ファセット検索などのようないくつかの操作が得意ではありません。
最近ではファセット検索は一般的なものになりました。amazon.comやebay.comのように多くのオンラインショッピングサイトがファセット検索をサポートしています。ファセット検索はユーザが検索結果を絞り込む前に絞り込み検索をし、その絞り込み検索の結果を表示します。ユーザは絞りこまれた結果から自分が探しているものを選ぶだけです。ファセット検索を使うとユーザは以下のようなメリットがあります。
ユーザはどうやって検索結果を絞り込むかを考える必要はありません。単に表示された絞り込み結果から選ぶだけです。
ユーザは「見つかりませんでした」ページを見ることがありません。ファセット検索では絞り込んだ結果のうち、ヒットする項目がある結果だけを表示します。
絞り込み検索は検索結果に対して複数の GROUP BY 操作を実行する必要があります。SQLでファセット検索をやろうとすると、複数の SELECT リクエストが必要になります。これは効率的ではありません。
Groongaは1回のgroongaコマンドでファセット検索をできます。これは効率的です。Groongaには select コマンドというファセット検索に対応した検索コマンドがあります。Groongaではファセット検索は ドリルダウン(drilldown) と呼ばれています。Groongaの select コマンドの詳細については Groongaのドキュメント を参照してください。
mroongaは mroonga_command() 関数を提供しています。この関数を使えばSQLの中で好きなgroongaコマンドを実行できます。しかし、使うのは select コマンドだけにしておくべきです。スキーマやデータを変更するコマンドを使うと一貫性が壊れてしまうかもしれません。
実行例で使用するスキーマ定義はこちら:
CREATE TABLE diaries (
id INT PRIMARY KEY AUTO_INCREMENT,
content VARCHAR(255),
date DATE,
year YEAR,
`year_month` VARCHAR(9),
tag VARCHAR(32),
FULLTEXT INDEX (content)
) ENGINE = Mroonga DEFAULT CHARSET utf8;
実行例で使用するサンプルデータはこちら:
INSERT INTO diaries (content, date, year, `year_month`, tag)
VALUES ('Groonga is an open-source fulltext search engine and column store.',
'2013-04-08',
'2013',
'2013-04',
'groonga');
INSERT INTO diaries (content, date, year, `year_month`, tag)
VALUES ('Mroonga is an open-source storage engine for fast fulltext search with MySQL.',
'2013-04-09',
'2013',
'2013-04',
'MySQL');
INSERT INTO diaries (content, date, year, `year_month`, tag)
VALUES ('Tritonn is a patched version of MySQL that supports better fulltext search function with Senna.',
'2013-03-29',
'2013',
'2013-03',
'MySQL');
各レコードは tag として groonga と MySQL が付いています。各レコードは year と year_month も持っています。ファセット検索のキーとして tag 、 year 、 year_month を使えます。
Groongaはファセット検索のことをドリルダウンと呼んでいます。そのため、Groongaでのパラメータ名は --drilldown となっています。groongaは検索結果をJSONで返します。そのため、 mroonga_command() も検索結果をJSONで返します。これはSQLらしくありません。JSON形式の検索結果は自分でパースしないといけません。
以下は利用可能なファセット検索キーをすべて使った例です。(結果のJSONは整形済み):
SELECT mroonga_command("select diaries --output_columns _id --limit 0 --drilldown tag,year,year_month") AS faceted_result;
+-----------------------------+
| faceted_result |
+-----------------------------+
| [[[3], |
| [["_id","UInt32"]]], |
| [[2], |
| [["_key","ShortText"], |
| ["_nsubrecs","Int32"]], |
| ["groonga",1], |
| ["MySQL",2]], |
| [[1], |
| [["_key","Time"], |
| ["_nsubrecs","Int32"]], |
| [1356998400.0,3]], |
| [[2], |
| [["_key","ShortText"], |
| ["_nsubrecs","Int32"]], |
| ["2013-04",2], |
| ["2013-03",1]]] |
+-----------------------------+
1 row in set (0.00 sec)
詳細は Groongaのselectコマンドのドキュメント を確認してください。
4.3.11. ログ出力¶
Mroongaではデフォルトでログの出力を行うようになっています。
ログファイルはMySQLのデータディレクトリ直下に groonga.log
というファイル名で出力されます。
以下はログの出力例です。
2010-10-07 17:32:39.209379|n|b1858f80|mroonga 1.10 started.
2010-10-07 17:32:44.934048|d|46953940|hash get not found (key=test)
2010-10-07 17:32:44.936113|d|46953940|hash put (key=test)
ログのデフォルトの出力レベルはNOTICE(必要な情報のみ出力。デバッグ情報などは出力しない)となっております。
ログの出力レベルは mroonga_log_level
というシステム変数で確認することができます(グローバル変数)。またSET文で動的に出力レベルを変更することもできます。
mysql> SHOW VARIABLES LIKE 'mroonga_log_level';
+-------------------+--------+
| Variable_name | Value |
+-------------------+--------+
| mroonga_log_level | NOTICE |
+-------------------+--------+
1 row in set (0.00 sec)
mysql> SET GLOBAL mroonga_log_level=DUMP;
Query OK, 0 rows affected (0.00 sec)
mysql> SHOW VARIABLES LIKE 'mroonga_log_level';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| mroonga_log_level | DUMP |
+-------------------+-------+
1 row in set (0.00 sec)
設定可能なログレベルは以下の通りです。
- NONE
- EMERG
- ALERT
- CRIT
- ERROR
- WARNING
- NOTICE
- INFO
- DEBUG
- DUMP
詳細は mroonga_log_level を参照してください。
またFLUSH LOGSでログの再オープンを行うことができます。MySQLサーバを停止せずにログのローテートを行いたいような場合には、以下の手順で実行すると良いでしょう。
groonga.log
ファイルの名前を変更(OSコマンドのmvなどで)MySQLサーバに対して"FLUSH LOGS"を実行(mysqlコマンドあるいはmysqladminコマンドにて)
4.3.12. カラムの刈り込み¶
Groongaでは各カラムごとにファイルを分けてデータを格納する「カラムストア方式」が採用されており、Mroongaではこの特性を活かすためにテーブルアクセス時に必要なカラムに対してのみアクセスを行う実装を行っています。
この高速化の仕組みはMroonga内部で自動的に行われるため、特に設定などを行う必要はありません。
例えば以下のようにカラムが20個定義されているテーブルが存在するものと仮定します。
CREATE TABLE t1 (
c1 INT PRIMARY KEY AUTO_INCREMENT,
c2 INT,
c3 INT,
...
c11 VARCHAR(20),
c12 VARCHAR(20),
...
c20 DATETIME
) ENGINE = Mroonga DEFAULT CHARSET utf8;
この時、以下のようなSELECT文が発行される場合、MroongaではSELECT句およびWHERE句で参照しているカラムに対してのみデータの読み取りを行ってSQL文を処理します(内部的に不要なカラムに対してはアクセスしません)。
SELECT c1, c2, c11 FROM t1 WHERE c2 = XX AND c12 = "XXX";
このケースではc1,c2,c11,c12に対してのみアクセスが行われ、SQL文が高速に処理されることになります。
4.3.13. 行カウント高速化¶
COUNT(*)などの行カウントを行う場合と通常のSELECTによるデータ参照を行う場合に対して、従来よりMySQLではストレージエンジンの呼び出しを行う部分(=ストレージエンジンインタフェース)における区別が存在していないため、行数をカウントするだけで良いような場合にもレコードアクセス(SELECTの結果には含まれないデータへのアクセス)が行われる問題があります。
Mroongaの前身であるTritonn(MySQL+Senna)ではこの問題に対して"2indパッチ"という不要なレコードアクセスを省略する仕組みを独自に実装してこの性能問題を回避していました。
これに引き続き、Mroongaでも行カウントを高速化するための仕組みを実装しています。
例えば以下のSELECT文では不要なカラムデータの読み取りは省略され、必要最小限のコストで行カウントの結果を返すことができます。
SELECT COUNT(*) FROM t1 WHERE MATCH(c2) AGAINST("hoge");
この最適化処理が行われたかどうかはステータス変数で確認することもできます。:
mysql> SHOW STATUS LIKE 'Mroonga_count_skip';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| Mroonga_count_skip | 1 |
+--------------------+-------+
1 row in set (0.00 sec)
行カウント高速化の処理が行われる度に Mroonga_count_skip
ステータス変数がインクリメントされます。
備考:この高速化機能はインデックスを用いて実装されています。現在のところインデックスアクセスのみでレコードが特定できるパタンでのみ有効に機能します。
4.3.14. 全文検索時の ORDER BY LIMIT 高速化¶
一般的にMySQLでは"ORDER BY"はインデックス経由のレコード参照が行えればほぼノーコストで処理可能であり、"LIMIT"は検索結果が大量にヒットする場合でも処理対象を限定することでコストを一定に抑える効果があります。
しかし例えば全文検索のスコアの降順+LIMITのように"ORDER BY"の処理の際にインデックスが効かないクエリの場合、検索ヒット件数に比例したコストがかかってしまうため、特に大量の検索がヒットするようなキーワード検索においてクエリ処理に極端に時間がかかってしまうケースがあります。
Tritonnではこの問題に対して特に対応はできていませんでしたが、最新レポジトリではsen_records_sort関数を活用してSennaからの読み出しをスコアの降順に対応させることでSQLクエリからORDER BY句を取り除く(※スコア降順を指定していたケースに対してのみ有効)回避方法を導入しました。
Mroongaでも ORDER BY LIMIT を高速化するための仕組みを実装しています。
例えば以下のSELECT文では ORDER BY LIMIT は、groonga内で処理され、必要最小限のレコードだけをMySQLに返却しています。
SELECT * FROM t1 WHERE MATCH(c2) AGAINST("hoge") ORDER BY c1 LIMIT 1;
この最適化処理が行われたかどうかはステータス変数で確認することもできます。:
mysql> SHOW STATUS LIKE 'Mroonga_fast_order_limit';
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| Mroonga_fast_order_limit | 1 |
+--------------------------+-------+
1 row in set (0.00 sec)
ORDER BY LIMIT 高速化の処理が行われる度に Mroonga_fast_order_limit
ステータス変数がインクリメントされます。
備考:この高速化機能は、「select ... match against order by _score desc limit X, Y」を狙い撃ちした高速化で、現在のところ以下の条件が成立した場合に機能します。
where句がmatch...againstのみ
joinしていない
limitの指定がある
order byの指定がカラム(_id含む)またはwhere句に指定したmatch...againstである
脚注
[1] |
[2] | Groongaではトークナイザーと呼んでいる。 |