MySQL 5.7における中国語全文検索機能の実装

MySQL 5.7.6以前のバージョンでは、全文検索機能は英語のみに対応しており、中国語の全文検索をサポートしていませんでした。中国語の文章を検索するには、分かち書きツールを使用して文章を単語に分割し、データベースに保存する必要がありました。

しかし、MySQL 5.7.6からは、ngram全文パーサーが組み込まれ、中国語、日本語、韓国語の分かち書きがサポートされるようになりました。本記事では、MySQLバージョン5.7.24と、InnoDBストレージエンジンを使用します。

ngram全文パーサーの仕組み

ngramとは、テキスト内の連続するn文字のシーケンスを指します。ngram全文パーサーはテキストを分かち書きし、各単語を連続するn文字のシーケンスとして処理します。

例えば、「新年快乐」という文章をngram全文パーサーで分かち書きすると、以下のようになります。

n=1: '新', '年', '快', '乐' 
n=2: '新年', '年快', '快乐' 
n=3: '新年快', '年快乐' 
n=4: '新年快乐'

MySQLでは、グローバル変数ngram_token_sizeを使用してngramのnの値を設定します。この値の範囲は1から10で、デフォルト値2です。通常、ngram_token_sizeは検索したい単語の最小文字数に設定します。単一文字を検索する必要がある場合は、ngram_token_sizeを1に設定する必要があります。デフォルト値が2の場合、単一文字の検索では結果が得られません。中国語の単語は通常少なくとも2文字であるため、デフォルト値の2を使用することが推奨されます。

ngram_token_sizeの設定方法

【方法1】:mysqld起動時にパラメータを指定する方法

<strong>mysqld</strong> <strong>--ngram_token_size=2</strong>

【方法2】:MySQL設定ファイルmy.cnf(Linux/macOS)またはmy.ini(Windows)に設定を追加する方法

[mysql]
# mysqlクライアントのデフォルト文字セット設定
default-character-set=utf8mb4
[mysqld]
# ポート設定
port = 3306

server_id=100
# mysqlのインストールディレクトリ設定
basedir=/usr/local/mysql
# mysqlデータベースのデータ保存ディレクトリ設定
datadir=/usr/local/mysql/data
# 最大接続数設定
max_connections=200
# サーバー側のデフォルト文字セット設定
character-set-server=utf8mb4
# 新規テーブル作成時のデフォルトストレージエンジン設定
default-storage-engine=INNODB

# 全文検索のトークンサイズ設定
<strong>ngram_token_size</strong><strong>=2</strong>

全文インデックスの作成

1. サンプルテーブルの作成

まず、記事データを保存するためのテーブルを作成します。

CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  `body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
  PRIMARY KEY (`id`),
  FULLTEXT INDEX `ft_idx_title_body`(`title`, `body`) WITH PARSER `ngram`
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

-- サンプルデータの挿入
INSERT INTO `articles` VALUES 
(1, '社会主義核心価値値 1', '国家の繁栄を栄光とし、国家の衰退を恥とする'),
(2, '社会主義核心価値値 2', '人民への奉仕を栄光とし、人民への裏切りを恥とする'),
(3, '社会主義核心価値値 3', '科学の尊重を栄光とし、無知を恥とする'),
(4, '社会主義核心価値値 4', '勤勉な労働を栄光とし、怠惰を恥とする'),
(5, '社会主義核心価値値 5', '団結と相互扶助を栄光とし、他人を犠牲にして利益を得ることを恥とする'),
(6, '社会主義核心価値値 6', '誠実さと信頼を栄光とし、利益のために正義を忘れることを恥とする'),
(7, '社会主義核心価値値 7', '法を遵守することを栄光とし、法を破ることを恥とする'),
(8, '社会主義核心価値値 8', '勤勉と節約を栄光とし、贅沢と浪費を恥とする'),
(9, '満江红', '靖康の恥、まだ雪がれていない'),
(10, '第一の生産力', '科学技術は第一の生産力である'),
(11, '団結互助', '団結は力なり'),
(12, '青と赤', '赤と黒'),
(13, '新年の挨拶', '明けましておめでとうございます'),
(14, '新年快乐', '良いお年を');

2. 全文インデックスの作成

タイトルと本文の両方を対象とする全文インデックスを作成します。

ALTER TABLE `articles` ADD FULLTEXT INDEX ft_idx_title_body(`title`, `body`) WITH PARSER ngram;

全文検索クエリの実行

1) タイトルまたは本文に「国家」を含むレコードの検索

SELECT *, MATCH (title, body) AGAINST ('国家') AS relevance
FROM articles 
WHERE MATCH (title, body) AGAINST ('国家' IN NATURAL LANGUAGE MODE);

2) タイトルまたは本文に「団結労働」を含むレコードの検索

SELECT *, MATCH (title, body) AGAINST ('団結労働') AS relevance
FROM articles 
WHERE MATCH (title, body) AGAINST ('団結労働' IN NATURAL LANGUAGE MODE);

検索結果は、関連性スコア(relevance)が高い順に自動的にソートされます。

3) タイトルまたは本文に「栄光」を含むレコードの検索

SELECT *, MATCH (title, body) AGAINST ('栄光') AS relevance
FROM articles 
WHERE MATCH (title, body) AGAINST ('栄光' IN NATURAL LANGUAGE MODE);

特殊なケース

1) 単一文字の検索

例えば、タイトルまたは本文に「力」を含むレコードを検索する場合:

SELECT *, MATCH (title, body) AGAINST ('力') AS relevance
FROM articles 
WHERE MATCH (title, body) AGAINST ('力' IN NATURAL LANGUAGE MODE);

このクエリでは結果が返されません。これは、グローバル変数ngram_token_sizeの値が2に設定されているためです。

単一文字を検索するには、設定ファイルでngram_token_size = 1に変更し、mysqldサービスを再起動する必要があります。

現在のngram_token_size設定を確認するには、以下のコマンドを使用します:

SHOW VARIABLES LIKE 'ngram_token_size';

ngram_token_sizeを1に設定した後、再度単一文字「力」の検索を実行すると、結果が得られます。IDが10のレコードには「力」が2回含まれ、IDが11のレコードには1回しか含まれていないため、前者のスコアは後者の2倍になります。

2) 単一フィールドの検索

例えば、本文フィールドのみに「誠実」を含むレコードを検索する場合:

SELECT *, MATCH (body) AGAINST ('誠実') AS relevance
FROM articles 
WHERE MATCH (body) AGAINST ('誠実' IN NATURAL LANGUAGE MODE);

このクエリは以下のエラーを返します:

ERROR 1191 (HY000): Can't find FULLTEXT index matching the column list

これは、本文フィールドに単独の全文検索インデックスが作成されていないためです。

本文フィールドに全文検索インデックスを作成するには:

ALTER TABLE `articles` ADD FULLTEXT INDEX ft_idx_body(`body`) WITH PARSER ngram;

再度、本文フィールドに「誠実」を含むレコードを検索すると、正しい結果が得られます。

同様に、タイトルフィールド単独で全文検索を行うには、タイトルフィールドにも全文検索インデックスを作成する必要があります:

ALTER TABLE `articles` ADD FULLTEXT INDEX ft_idx_title(`title`) WITH PARSER ngram;

全文検索モード

MySQLの全文検索には、主に2つのモードがあります。

1. 自然言語モード(NATURAL LANGUAGE MODE)

自然言語モードはMySQLのデフォルトの全文検索モードです。このモードでは、演算子を使用できず、キーワードが必ず出現するか、または出現しないといった複雑なクエリを指定することはできません。

2. ブールモード(BOOLEAN MODE)

ブールモードでは、演算子を使用して、キーワードが必ず出現するか、出現しない、またはキーワードの重み付けなど、複雑なクエリをサポートします。

ブールモードの演算子の例:

'りんご みかん' 
演算子なし:りんごまたはみかんを含む

'+りんご +ジュース'
両方の単語を必ず含む

'+りんご パソコン'
りんごを必ず含み、パソコンも含む場合は関連性が更高

'+りんご -パソコン'
りんごを必ず含み、パソコンは含まない

'+りんご ~パソコン'
りんごを必ず含むが、パソコンも含む場合は含まない場合より関連性が低い

'+りんご +(>ジュース <パイ)'
りんごとジュース、またはりんごとパイを含むが、りんごとジュースの組み合わせの方がりんごとパイより関連性が高い

'りんご*'
りんごで始まる単語を含む(りんご、りんごジュースなど)

'"いくつかの単語"'
二重引用符で囲むと、LIKE '%いくつかの単語%'に似た効果
「いくつかの単語の知恵」はマッチするが、「いくつかの雑音単語」はマッチしない

ブールモードの使用例

1) 本文フィールドに「団結」と「力」の両方を含むレコードの検索:

SELECT *, MATCH (body) AGAINST ('+団結 +力') AS relevance
FROM articles 
WHERE MATCH (body) AGAINST ('+団結 +力' <strong>IN BOOLEAN MODE</strong>);

2) 本文フィールドに「団結」を含み、「力」を含まないレコードの検索:

SELECT *, MATCH (body) AGAINST ('+団結 -力') AS relevance
FROM articles 
WHERE MATCH (body) AGAINST ('+団結 -力' <strong>IN BOOLEAN MODE</strong>);

3) 本文フィールドに「団結」または「力」のいずれかを含むレコードの検索:

SELECT *, MATCH (body) AGAINST ('団結 力') AS relevance
FROM articles 
WHERE MATCH (body) AGAINST ('団結 力' <strong>IN BOOLEAN MODE</strong>);

注意事項

  1. 全文インデックスは、CHARVARCHAR、またはTEXT型のフィールドにのみ作成できます。
  2. 全文インデックスは、InnoDBMyISAMエンジンでのみサポートされています。
  3. MATCH(columnName) AGAINST('keywords')関数で使用するフィールド名は、全文インデックス作成時に指定したフィールド名と一致している必要があります
  4. MATCH()関数で使用するフィールド名は同じテーブルのフィールドのみ可能です。全文インデックスは複数のテーブルにまたがる検索をサポートしていません。
  5. 大量のデータセットをインポートする場合、テーブルに全文インデックスを作成してからデータをインポートするよりも、先にデータをインポートしてから全文インデックスを作成する方がはるかに高速です。

タグ: MySQL 全文検索 ngram InnoDB 中国語テキスト処理

7月13日 17:59 投稿