SQLite検索が遅い?LIKEの限界とFTS5で高速化する方法:内部処理も解説
はじめに
以前の記事では、Pythonで使えるデータベースを比較しました。その中でも「軽量で扱いやすい」と人気なのがSQLiteです。しかし、テキスト検索を多用する処理でSQLiteを使うと、「LIKE '%keyword%'が遅い」「検索に数秒かかる」など、性能面での課題が見えてきます。そこで今回は、SQLiteの全文検索拡張 FTS5(Full Text Search 5) を取り上げ、LIKE検索の限界とFTS5による高速化の仕組みを解説します。
1. SQLiteの課題と全文検索という発想
SQLiteは軽量ながら強力なデータベースですが、検索性能には特徴があります。
- 完全一致・前方一致(
LIKE 'abc%')は、高速 - 中間・後方一致(
LIKE '%abc%')は、遅い(全件走査)
この「遅さ」を解決するのが全文検索(Full Text Search) です。全文検索では、テキストをあらかじめ単語単位で分解・インデックス化(検索を速くするための「索引表」を作成)しておき、「どの文書にどんな単語が含まれているか」を高速に参照します。SQLiteでは、この全文検索を可能にする拡張として FTS5 が標準搭載されています。
2.FTS5の特徴(SQLiteとの比較)
FTS5はSQLiteに組み込まれた拡張モジュールで、仮想テーブルとして動作します。
以下のように、目的・構造・検索方式が異なります。
| 項目 | SQLite 通常テーブル | FTS5 仮想テーブル |
|---|---|---|
| 検索方式 |
LIKE, = など |
MATCH 演算子 |
| 検索速度 | 遅い(全件走査) | 速い(インデックス検索) |
| 検索対象 | 完全一致・部分一致 | 単語単位での全文検索 |
| 検索結果 | ヒットの有無のみ | 関連度スコアも取得可能 |
| 主な用途 | 数値・構造データ | テキスト・自然言語データ |
また、FTS5を使うか通常のSQLiteのインデックスを使うかは、データ特性で判断します。
| 検索対象や処理概要など | SQLiteの通常インデックス | FTS5(全文検索) |
|---|---|---|
| 短い文字列・コード・ID | ◎ 最適 | △ 過剰 |
完全一致・前方一致(WHEREcol = ? / col LIKE 'abc%') |
◎ 速い | △ |
| 後方/部分一致 ( LIKE '%abc', '%abc%') |
△(工夫が必要) | ◎ 本来想定された用途 |
| 複数語のAND/OR/NOT、 スコア付け |
△(手作りが大変) | ◎ 標準機能 |
| 長文を横断検索 | △ | ◎ 本来想定された用途 |
ソートや集計(ORDER BY,GROUP BY など) |
◎ | △(別テーブル併用が基本) |
| 追記や更新の頻度が高い | ◎ | △(インデックス再構築の 負荷に注意) |
| 日本語(分かち書きなし)の自然文 | △ | ◎(要工夫) |
3.FTS5の使い方
FTS5を使うには、SQLiteで仮想テーブルを作成します。
-- FTS5テーブルの作成
CREATE VIRTUAL TABLE articles USING fts5(title, content);
-- データ挿入
INSERT INTO articles (title, content)
VALUES ('AIニュース', 'ChatGPTや生成AIが話題になっている');
-- 検索実行
SELECT title, content FROM articles
WHERE articles MATCH '生成AI';
MATCH 演算子を使うことで、LIKEよりも圧倒的に高速に検索できます。FTS5は内部で「逆引きインデックス」を構築しているため、キーワードが含まれる行を即座に特定できます。
fts5を使うには、有効化する必要があります。
fts5は仮想テーブルなので、実データはsqliteで保持する必要があります。sqliteのデータをfts5にコピーして使います。
4.日本語の全文検索を行うときの注意点
SQLiteのFTS5は英語向けに設計されており、日本語のように、単語の区切りが明示されない言語ではそのままでは正確に検索できません。例えば、生成AIが話題になっているをそのまま登録すると、この文全体が1トークンとみなされ、MATCH 'AI' ではヒットしません。
そこで、日本語を扱う場合は以下のどちらかの手法を採用することが多いです。
| 方法 | 概要 | メリット | デメリット |
|---|---|---|---|
| ① n-gram方式(2-gram, 3-gramなど) | 文字をn文字単位で分割 | SQLiteだけで導入可能 | ノイズが多い |
| ② トークナイザ拡張(MeCab, Kuromojiなど) | 形態素解析により単語単位で分割 | 精度が高い | 導入がやや複雑 |
このように unicode61 トークナイザを使い、2文字単位で区切ることで、日本語でも部分一致検索が可能になります。
5.おまけ:内部処理に関する解説
5-1.なぜLIKE検索は遅いのか(全件走査の仕組み)
SQLiteの通常検索で部分一致を行うときの LIKE '%keyword%' は、全件走査を行います。
SELECT * FROM articles WHERE content LIKE '%AI%';
このときSQLiteは以下のように動きます。
- テーブル内の全行を順に読み取る
- 各行の文字列を先頭から末尾までチェック
- 一致した行だけを返す
つまり、インデックスを使わずすべての文字列を逐次照合します。データ量が増えるほど検索時間は線形的に増加し、数万件を超えると体感でも「遅い」と感じるレベルになります。一方で、前方一致(LIKE 'AI%')はインデックスを利用できるため高速です。問題は、ワイルドカード(%)が先頭にある検索ではインデックスが無効化されることです。
5-2.FTS5の内部構造:インデックスとトークナイザ
FTS5は、以下のような全文検索専用の仕組みを内部に持っています。
-
トークナイザ
テキストを単語単位に分割する処理です。
例:「生成AIが普及している」 → 「生成」「AI」「普及」「して」「いる」 -
インデックス構築
テキストを登録すると、FTS5は内部で次のような表を作り、単語ごとに「どの文書に現れるか」を記録します。
| 単語 | 出現した文書ID(rowid) |
|---|---|
| 生成 | [1, 5, 9] |
| AI | [1, 3, 7] |
| 普及 | [1, 4] |
これが「逆引きインデックス」と呼ばれる構造で、検索時には該当単語の文書リストを高速に取得できます。さらに、FTS5は出現頻度などからスコアを計算し、関連度順に結果を並べ替えることもできます。これによりMATCH 'AI AND 普及'のような複雑な検索でも一瞬で処理が完了します。
おわりに
SQLiteは軽量で便利なデータベースですが、テキスト検索には限界があります。FTS5を使うことで、高速な全文検索・スコアリング・複雑検索(AND/OR/NOT) が可能になり、シンプルな環境でも「検索エンジンのような体験」を実現できます。
Discussion