ここでは mroonga におけるストレージモードの利用方法を説明します。
インストールが確認できたら、テーブルを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)
おぉぉー。検索できましたね。
ノート
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)
MySQLは全文検索用のパーサ [2] を指定する以下のような構文を持っています。:
FULLTEXT INDEX (content) WITH PARSER parser_name
しかし、この構文を利用する場合は、あらかじめすべてのパーサをMySQLに登録しておく必要があります。一方、groongaはトークナイザー(MySQLでいうパーサ)を動的に追加することができます。そのため、mroognaでもこの構文を採用するとgroonga側に動的に追加されたトークナイザーに対応できなくなります。groongaに動的に追加されるトークナイザーにはMeCabを用いたトークナイザーもあり、この制限に縛られることは利便性を損なうと判断し、以下のようなコメントを用いた独自の構文を採用することにしました。:
FULLTEXT INDEX (content) COMMENT 'parser "TokenMecab"'
ノート
FULLTEXT INDEX に COMMENT を指定できるのはMySQL 5.5からになります。MySQL 5.1を利用している場合は後述の mroonga_default_parser 変数を利用してください。
パーサに指定できるのは以下の値です。
トークナイズしません。"off"は``content``をそのまま扱いたい場合に使います。例えば、この値は前方一致検索のために指定します。
バイグラムでトークナイズする。ただし、連続したアルファベット・連続した数字・連続した記号はそれぞれ1つのトークンとして扱う。そのため、3文字以上のトークンも存在する。これはノイズを減らすためである。
デフォルト値。
MeCabを用いてトークナイズする。groongaがMeCabサポート付きでビルドされている必要がある。
バイグラムでトークナイズする。TokenBigramと異なり、記号が連続していても特別扱いして1つのトークンとして扱わず通常のバイグラムの処理を行う。
TokenBigramではなくTokenBigramSplitSymbolを利用すると「Is it really!?!?!?」の「!?!?!?」の部分に「!?」でマッチする。TokenBigramの場合は「!?!?!?」でないとマッチしない。
バイグラムでトークナイズする。TokenBigramSplitSymbolに加えて、連続したアルファベットも特別扱いせずに通常のバイグラムの処理を行う。
TokenBigramではなくTokenBigramSplitSymbolAlphaを利用すると「Is it really?」に「real」でマッチする。TokenBigramの場合は「really」でないとマッチしない。
バイグラムでトークナイズする。TokenBigramSplitSymbolAlphaに加えて、連続した数字も特別扱いせずに通常のバイグラムの処理を行う。つまり、すべての字種を特別扱いせずにバイグラムの処理を行う。
TokenBigramではなくTokenBigramSplitSymbolAlphaDigitを利用すると「090-0123-4567」に「567」でマッチする。TokenBigramの場合は「4567」でないとマッチしない。
バイグラムでトークナイズする。TokenBigramと異なり、空白を無視して処理する。
TokenBigramではなくTokenBigramIgnoreBlankを利用すると「み な さ ん 注 目」に「みなさん」でマッチする。TokenBigramの場合は「み な さ ん」でないとマッチしない。
バイグラムでトークナイズする。TokenBigramSplitSymbolと異なり、空白を無視して処理する。
TokenBigramSplitSymbolではなくTokenBigramIgnoreBlankSplitSymbolを利用すると「! !? ??」に「???」でマッチする。TokenBigramSplitSymbolの場合は「? ??」でないとマッチしない。
バイグラムでトークナイズする。TokenBigramSplitSymbolAlphaと異なり、空白を無視して処理する。
TokenBigramSplitSymbolAlphaではなくTokenBigramIgnoreBlankSplitSymbolAlphaを利用すると「I am a pen.」に「ama」でマッチする。TokenBigramSplitSymbolAlphaの場合は「am a」でないとマッチしない。
バイグラムでトークナイズする。TokenBigramSplitSymbolAlphaDigitと異なり、空白を無視して処理する。
okenBigramSplitSymbolAlphaDigitではなくTokenBigramIgnoreBlankSplitSymbolAlphaDigitを利用すると「090 0123 4567」に「9001」でマッチする。TokenBigramSplitSymbolAlphaDigitの場合は「90 01」でないとマッチしない。
空白区切りでトークナイズする。
"movie horror topic" will be tokenised as "movie", "horror", "topic".
null文字(\0)区切りでトークナイズする。
"movie\0horror\0topic" will be tokenised as "movie", "horror", "topic".
ユニグラムでトークナイズする。ただし、連続したアルファベット・連続した数字・連続した記号はそれぞれ1つのトークンとして扱う。そのため、2文字以上のトークンも存在する。これはノイズを減らすためである。
トリグラムでトークナイズする。ただし、連続したアルファベット・連続した数字・連続した記号はそれぞれ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)
ストレージモードでは全文検索だけではなく位置情報検索も高速に実行できます。ただし、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)
位置情報で検索できていますね!
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
ノート
この機能は実験的です。引数や引数の値が予告なく変更される可能性があります。
キーワードと周辺のテキストを検索結果として取得したいことがあります。
スニペットは'キーワードと関連するテキスト'を意味します。文脈付き索引ともいいます。
mroonga_snippet 関数は検索結果からスニペットを取得する方法を提供します。
mroonga_snippet 関数の構文:
SELECT mroonga_snippet(document, max_length, max_count, encoding,
skip_leading_spaces, html_escape, prefix, suffix,
word1, word1_prefix, word1_suffix,
word2, word2_prefix, word2_suffix, ...);
mroonga_snippet の引数の詳細です。
カラム名もしくは文字列を指定。
スニペットの長さの最大バイト数を指定。
スニペットの最大個数を指定。
文書のエンコーディングを指定します。値として 'ascii_general_ci'、'cp932_japanese_ci'、'eucjpms_japanese_ci'、'utf8_japanese_ci'などが指定できます。
先頭の空白を無視するかを指定します。1なら無視し、0なら無視しません。
HTMLのエスケープを行うか指定します。1ならエスケープし、0ならそのまま出力します。
スニペットの開始テキストを指定。
スニペットの終了テキストを指定。
単語を指定。
N番目の単語の開始テキストを指定。
N番目の単語の開始テキストを指定。
mroonga_snippet 関数はユーザ定義関数(UDF)としてmroongaに含まれていますが、インストール時にCREATE FUNCTIONでMySQLに追加していない場合には、以下の関数定義DDLを実行しておく必要があります。
mysql> CREATE FUNCTION mroonga_snippet RETURNS STRING SONAME 'ha_mroonga.so';
mroonga_snippet 関数は MATCH ... AGAINST構文とともに使用して、テキストに含まれるキーワードとその関連語句を検索するのに便利です。
'fulltext'というキーワードと'MySQL'と'search'という関連語を含む文書の検索をしてみましょう。
mroonga_snippet 関数は上記のことができます。
実行例で使用するスキーマ定義はこちら:
CREATE TABLE `snippet_test` (
`id` int(11) NOT NULL,
`text` text,
PRIMARY KEY (`id`),
FULLTEXT KEY `text` (`text`)
) ENGINE=mroonga DEFAULT CHARSET=utf8
実行例で使用するサンプルデータはこちら:
insert into snippet_test (id, text) values (1, 'An open-source fulltext search engine and column store.');
insert into snippet_test (id, text) values (2, 'An open-source storage engine for fast fulltext search with MySQL.');
insert into snippet_test (id, text) values (3, 'Tritonn is a patched version of MySQL that supports better fulltext search function with Senna.');
実行結果はこちら:
mysql> select * from snippet_test;
+----+-------------------------------------------------------------------------------------------------+
| id | text |
+----+-------------------------------------------------------------------------------------------------+
| 1 | An open-source fulltext search engine and column store. |
| 2 | An open-source storage engine for fast fulltext search with MySQL. |
| 3 | Tritonn is a patched version of MySQL that supports better fulltext search function with Senna. |
+----+-------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)
mysql> select id, text, mroonga_snippet(text, 8, 2, 'ascii_general_ci', 1, 1, '...', '...<br>', 'fulltext', '<span class="w1">', '</span>', 'MySQL', '<span class="w2">', '</span>', 'search', '<span calss="w3">', '</span>') from snippet_test where match(text) against ('fulltext');
+----+-------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| id | text | mroonga_snippet(text, 8, 2, 'ascii_general_ci', 1, 1, '...', '...<br>', 'fulltext', '<span class="w1">', '</span>', 'MySQL', '<span class="w2">', '</span>', 'search', '<span calss="w3">', '</span>') |
+----+-------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 1 | An open-source fulltext search engine and column store. | ...<span class="w1">fulltext</span>...<br>... <span calss="w3">search</span> ...<br> |
| 2 | An open-source storage engine for fast fulltext search with MySQL. | ...<span class="w1">fulltext</span>...<br>... <span calss="w3">search</span> ...<br> |
| 3 | Tritonn is a patched version of MySQL that supports better fulltext search function with Senna. | ...f <span class="w2">MySQL</span> ...<br>...<span class="w1">fulltext</span>...<br> |
+----+-------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)
'fulltext'というキーワードと関連する'MySQL'と'search'が抽出できました。
ストレージモードでは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コマンドのドキュメント を確認してください。
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)
設定可能なログレベルは以下の通りです。
詳細は mroonga_log_level を参照してください。
またFLUSH LOGSでログの再オープンを行うことができます。MySQLサーバを停止せずにログのローテートを行いたいような場合には、以下の手順で実行すると良いでしょう。
groonga.log ファイルの名前を変更(OSコマンドのmvなどで)
MySQLサーバに対して"FLUSH LOGS"を実行(mysqlコマンドあるいはmysqladminコマンドにて)
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文が高速に処理されることになります。
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 ステータス変数がインクリメントされます。
備考:この高速化機能はインデックスを用いて実装されています。現在のところインデックスアクセスのみでレコードが特定できるパタンでのみ有効に機能します。
一般的に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ではトークナイザーと呼んでいる。 |