MySQL 8.0 リファレンスマニュアル


12.10.1 自然言語全文検索

デフォルトの場合や IN NATURAL LANGUAGE MODE 修飾子が指定された場合は、MATCH() 関数は、テキストコレクションに対して文字列の自然言語検索を実行します。 コレクションは、FULLTEXT インデックスに含まれる 1 つ以上のカラムのセットです。 検索文字列は、AGAINST() への引数として指定されます。 MATCH() は、テーブルの行ごとに関連性の値を返します。つまり、検索文字列と、MATCH() リストで名前が指定されたカラムの該当行のテキスト間で類似性が評価されます。

mysql> CREATE TABLE articles (
          id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
          title VARCHAR(200),
          body TEXT,
          FULLTEXT (title,body)
        ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.08 sec)

mysql> INSERT INTO articles (title,body) VALUES
        ('MySQL Tutorial','DBMS stands for DataBase ...'),
        ('How To Use MySQL Well','After you went through a ...'),
        ('Optimizing MySQL','In this tutorial, we show ...'),
        ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
        ('MySQL vs. YourSQL','In the following database comparison ...'),
        ('MySQL Security','When configured properly, MySQL ...');
Query OK, 6 rows affected (0.01 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM articles
        WHERE MATCH (title,body)
        AGAINST ('database' IN NATURAL LANGUAGE MODE);
+----+-------------------+------------------------------------------+
| id | title             | body                                     |
+----+-------------------+------------------------------------------+
|  1 | MySQL Tutorial    | DBMS stands for DataBase ...             |
|  5 | MySQL vs. YourSQL | In the following database comparison ... |
+----+-------------------+------------------------------------------+
2 rows in set (0.00 sec)

デフォルトでは、大文字と小文字が区別される方法で検索が実行されます。 大/小文字を区別する全文検索を実行するには、インデックス付けされたカラムに大/小文字を区別する照合またはバイナリ照合を使用します。 たとえば、の utf8mb4 文字セットを使用するカラムには、utf8mb4_0900_as_cs または utf8mb4_bin の照合順序を割り当てて、全文検索で大文字と小文字を区別できます。

以前に例で示したように、MATCH()WHERE 句で使用されると、もっとも関連性の高い行が 1 番目に返されるように、自動的にソートされます。 関連性の値は、負ではない浮動小数点数です。 ゼロの関連性は、類似性がないという意味です。 関連性は、行 (ドキュメント) 内の単語の数、行内の一意の単語の数、コレクション内の単語の合計数および特定の単語を含む行の数に基づいて計算されます。

注記

document という用語は、row という用語と同じ意味で使用でき、どちらの用語も行のインデックス付けされた部分を指します。 collection という用語は、インデックス付けされたカラムを指し、すべての行を含みます。

単に一致をカウントするには、次のようなクエリーを使用してください。

mysql> SELECT COUNT(*) FROM articles
    WHERE MATCH (title,body)
    AGAINST ('database' IN NATURAL LANGUAGE MODE);
+----------+
| COUNT(*) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)

次のように、クエリーを再作成した方が早い場合もあります。

mysql> SELECT
    COUNT(IF(MATCH (title,body) AGAINST ('database' IN NATURAL LANGUAGE MODE), 1, NULL))
    AS count
    FROM articles;
+-------+
| count |
+-------+
|     2 |
+-------+
1 row in set (0.03 sec)

1 つ目のクエリーでは、いくつかの追加作業 (関連性別の結果のソート) が実行されますが、WHERE 句に基づくインデックス検索を使用することもできます。 インデックス検索では、検索でほとんど行が一致しない場合に、最初のクエリーが速くなる可能性があります。 2 つ目のクエリーではテーブルの完全スキャンが実行され、ほとんどの行に検索語句が存在した場合に、インデックス検索よりも高速になる可能性があります。

自然言語による全文検索では、MATCH() 関数で名前が指定されたカラムは、テーブル内の一部の FULLTEXT インデックスに含まれるカラムと同じである必要があります。 前述のクエリーでは、MATCH() 関数 (title および body) で指定されたカラムは、article テーブル FULLTEXT インデックスの定義で指定されたカラムと同じであることに注意してください。 title または body を個別に検索するには、カラムごとに個別の FULLTEXT インデックスを作成します。

ブール検索やクエリー拡張を使用した検索を実行することもできます。 これらの検索タイプについては、セクション12.10.2「ブール全文検索」およびセクション12.10.3「クエリー拡張を使用した全文検索」で説明されています。

インデックスを使用した全文検索では、インデックスが複数のテーブルに及ぶ可能性はないため、MATCH() 句の単一テーブルにあるカラムにしか名前を付けることができません。 MyISAM テーブルでは、インデックスが存在しない場合でも、ブール検索を実行できます (ただし、低速になります)。この場合、複数のテーブルのカラムに名前を指定できます。

上記の例では、関連性の降順で行が返される MATCH() 関数の使用方法について簡単に説明しました。 次の例では、関連性の値を明示的に取得する方法を示します。 SELECT ステートメントには WHERE 句も ORDER BY 句も含まれていないため、返される行は順序付けられません。

mysql> SELECT id, MATCH (title,body)
    AGAINST ('Tutorial' IN NATURAL LANGUAGE MODE) AS score
    FROM articles;
+----+---------------------+
| id | score               |
+----+---------------------+
|  1 | 0.22764469683170319 |
|  2 |                   0 |
|  3 | 0.22764469683170319 |
|  4 |                   0 |
|  5 |                   0 |
|  6 |                   0 |
+----+---------------------+
6 rows in set (0.00 sec)

次の例はさらに複雑です。 このクエリーでは関連性の値が返され、関連性の降順での行のソートも行われます。 この結果を実現するには、MATCH() を 2 回 (1 回は SELECT リストに、もう 1 回は WHERE 句に) 指定します。 MySQL オプティマイザによって、2 回の MATCH() 呼び出しが同じであり、全文検索コードが 1 回のみ起動されることが検出されるため、追加のオーバーヘッドは発生しません。

mysql> SELECT id, body, MATCH (title,body) AGAINST
    ('Security implications of running MySQL as root'
    IN NATURAL LANGUAGE MODE) AS score
    FROM articles WHERE MATCH (title,body) AGAINST
    ('Security implications of running MySQL as root'
    IN NATURAL LANGUAGE MODE);
+----+-------------------------------------+-----------------+
| id | body                                | score           |
+----+-------------------------------------+-----------------+
|  4 | 1. Never run mysqld as root. 2. ... | 1.5219271183014 |
|  6 | When configured properly, MySQL ... | 1.3114095926285 |
+----+-------------------------------------+-----------------+
2 rows in set (0.00 sec)

二重引用符 (") 文字内で囲まれたフレーズは、入力されたそのままのフレーズを含む行にのみ一致します。 全文エンジンでは、フレーズが複数の単語に分割され、それらの単語の FULLTEXT インデックス内で検索が実行されます。 単語以外の文字は、正確に一致する必要がありません。フレーズ検索では、そのフレーズとまったく同じ単語が同じ順序で一致に含まれることのみが必要です。 たとえば、"test phrase""test, phrase" と一致します。 フレーズにインデックス内にある単語が含まれない場合は、結果が空になります。 たとえば、すべての単語がストップワードであるか、インデックス付けされた単語の最小長より短い場合、結果は空になります。

MySQL FULLTEXT の実装では、トゥルーワード文字 (文字、数字、およびアンダースコア) のシーケンスが単語とみなされます。 そのシーケンスには、アポストロフィー (') も含めることはできますが、1 行に 1 つまでです。 つまり、aaa'bbb は 1 語とみなされますが、aaa''bbb は 2 語とみなされます。 単語の先頭または末尾のアポストロフィーは、FULLTEXT パーサーによって削除されます。'aaa'bbb' は、aaa'bbb として解析されます。

組込み FULLTEXT パーサーは、 (空白)、, (カンマ)、. (ピリオド) などの特定のデリミタ文字を検索して、単語の開始位置と終了位置を決定します。 単語がデリミタで区切られていない場合 (中国語など)、組込み FULLTEXT パーサーは単語の開始位置または終了位置を判別できません。 組込み FULLTEXT パーサーを使用する FULLTEXT インデックスに、このような言語の単語またはインデックス付けされた他の用語を追加できるようにするには、それらが任意のデリミタで区切られるように事前処理する必要があります。 または、ngram パーサープラグイン (中国語、日本語または韓国語) または MeCab パーサープラグイン (日本語) を使用して、FULLTEXT インデックスを作成できます。

組み込みの全文パーサーを置き換えるプラグインを記述できます。 詳細は、The MySQL Plugin APIを参照してください。 パーサープラグインのサンプルソースコードについては、MySQL のソース配布の plugin/fulltext ディレクトリを参照してください。

全文検索では、一部の単語が無視されます。

  • 短すぎる単語は無視されます。 全文検索で見つかった単語のデフォルトの最小長は、InnoDB 検索インデックスの場合は 3 文字、MyISAM の場合は 4 文字です。 インデックスを作成する前に、構成オプション (InnoDB 検索インデックスの場合は innodb_ft_min_token_size 構成オプション、MyISAM の場合は ft_min_word_len) を設定すると、カットオフを制御できます。

    注記

    この動作は、ngram パーサーを使用する FULLTEXT インデックスには適用されません。 ngram パーサーの場合、トークンの長さは ngram_token_size オプションによって定義されます。

  • ストップワードリスト内の単語は無視されます。 ストップワードは、セマンティクス値がゼロであると考えられるほど一般的な単語 (thesome など) です。 組み込みのストップワードリストもありますが、ユーザー定義のリストでオーバーライドできます。 ストップワードリストおよび関連する構成オプションは、InnoDB 検索インデックスと MyISAM 検索インデックスとで異なります。 ストップワードの処理は、InnoDB 検索インデックスの場合は構成オプション innodb_ft_enable_stopwordinnodb_ft_server_stopword_table、および innodb_ft_user_stopword_tableMyISAM 検索インデックスの場合は ft_stopword_file によって制御されます。

デフォルトのストップワードリストおよびそれを変更する方法を表示する方法については、セクション12.10.4「全文ストップワード」を参照してください。 単語のデフォルト最小長は、セクション12.10.6「MySQL の全文検索の微調整」で説明したように変更できます。

コレクションおよびクエリー内のすべての正確な単語は、コレクションまたはクエリーでの重要性に従って重み付けられます。 したがって、多くのドキュメント内に存在する単語では、この特定のコレクション内のセマンティクス値が低くなるため、重みも低くなります。 反対に、まれな単語には、高い重みが付けられます。 単語の重みを組み合わせることで、行の関連性が計算されます。 この技術は、大きなコレクションで最適に機能します。

MyISAM の制限

非常に小さなテーブルでは、単語の配布が適切にセマンティクス値に反映されないため、このモデルでは、MyISAM テーブル上の検索インデックスに対して異常な結果が生成される可能性があります。 たとえば、以前に示した articles テーブルのすべての行には、MySQL という単語が存在しますが、MyISAM 検索インデックス内の単語を検索しても結果が生成されません。

mysql> SELECT * FROM articles
    WHERE MATCH (title,body)
    AGAINST ('MySQL' IN NATURAL LANGUAGE MODE);
Empty set (0.00 sec)

MySQL という単語は 50% 以上の行に存在するため、検索の結果が空になります。そのため、事実上ストップワードとして処理されます。 このフィルタ処理技術は、よくある語句に対して不適切な結果が生成される可能性のある小さなデータセットよりも、1G バイトのテーブルから 1 行おきに結果セットが返される可能性のない大きなデータセットに適しています。

全文検索がどのように動作するかを確認するために最初に試すと、しきい値が 50% であることに驚くかもしれませんが、これによって InnoDB テーブルが全文検索での実験によりふさわしいことがわかります。 MyISAM テーブルを作成し、それに 1、2 行のテキストのみを挿入する場合は、テキスト内のすべての単語が 50% 以上の行に出現します。 その結果、テーブルにより多くの行が含まれるまで、検索の結果が返されません。 50% の制限を回避する必要のあるユーザーは、InnoDB テーブル上に検索インデックスを構築したり、セクション12.10.2「ブール全文検索」で説明したブール検索モードを使用したりできます。


関連キーワード:  関数, インデックス, 単語, 検索, テーブル, FULLTEXT, クエリー, カラム, 空間, リファレンス