AetherEchoesEngineering
Vol.042026年5月9日
Engineering#00571 min5555 view

MySQL FULLTEXT ngram で日本語 AND 検索を実装する

ngram_token_size / IN BOOLEAN MODE / +演算子。Elasticsearch を使わずに日本語 AND 検索を MySQL だけで完結させる。

SoSoraEndo2026年5月9日1 min555

検索を入れる前提

個人ブログ規模で「日本語検索が動く」ところまで到達する道は概ね 3 つ:

  1. MySQL LIKE — 単純、一致率は低い
  2. MySQL FULLTEXT + ngram parser — 日本語に対応した built-in 全文検索
  3. Meilisearch / OpenSearch — 専用エンジン

最初の 1 本目は (1)、本気を出すなら (3)。中間で「悪くない検索」を実装するなら (2)。

私は (2) を雑に動かす 方針を取った。50〜500 記事規模の個人サイトなら、これで十分体感できる検索になる。

ngram parser を入れる

MySQL 8.x は ngram parser が built-in。インデックス作成時に指定する:

ALTER TABLE posts
  ADD FULLTEXT INDEX idx_posts_fulltext (title, excerpt, body_markdown)
  WITH PARSER ngram;

ngram parser はデフォルト 2-gram。日本語の検索ではこれでだいたい問題なく動く。

クエリ

SELECT *,
       MATCH(title, excerpt, body_markdown)
         AGAINST('Rails 8 移行' IN BOOLEAN MODE) AS score
  FROM posts
 WHERE MATCH(title, excerpt, body_markdown)
         AGAINST('Rails 8 移行' IN BOOLEAN MODE)
   AND status = 2
 ORDER BY score DESC
 LIMIT 20;

IN BOOLEAN MODE でないと、+keyword -keyword のような演算子が効かない。空白区切りの語は OR で結合される(natural language mode は AND だが、ngram の挙動が独特)。

AND 検索したい場合

複数語の AND は手動で組む:

def search(query, scope: Post.published)
  terms = query.strip.split(/\s+/).reject(&:empty?).first(5)
  return scope if terms.empty?

  match_clauses = terms.map do |t|
    sanitized = ActiveRecord::Base.connection.quote(t)
    "MATCH(title, excerpt, body_markdown) AGAINST(#{sanitized} IN BOOLEAN MODE)"
  end

  scope.where(match_clauses.join(' AND '))
end

語ごとに MATCH ... AGAINST ... を作って AND で結合。これで「複数キーワードの AND 検索」が動く。

落とし穴

実運用で気づいた点 3 つ:

  1. 2 文字以下の検索語は ngram parser で扱えないinnodb_ft_min_token_size のデフォルトが 2)。「Go」「Rails」のような 2 文字以下を検索したい場合は LIKE でフォールバック。
  2. score ベースのソート は記事数が少ないと安定しない。50 記事規模だと「relevance を期待した結果」と「最新順」がほぼ同じになる。50 を超えると初めて relevance が効く。
  3. 空白の正規化: 全角スペースを半角に変換。これを忘れると「Rails 8 移行」(全角)が hit しない。

卒業のタイミング

500 記事を超えるか、ファセット検索が欲しくなったら Meilisearch に移行。1000 記事 / 10ms 以内が体感できる。それ以下では MySQL ngram で十分。

検索は「無いと不便、あるとそこそこ」の機能。最初は MySQL で雑に動かして、書きながら必要性を実測するのがよい。

Tags

Reaction

Share

X (Twitter)