📘

TiDB Serverless の全文検索機能を試してみた

に公開

先日PingCapの関口さんがこんな投稿をされていました。
https://x.com/bohnen/status/1922582563631026389
このブログを書いている2025/6/1時点でTokyoリージョンではまだ使えないようですが、フランクフルトリージョンでは使えるようです。と、いうことでさっそく試してみました。

さっそくやってみる

公式の以下のドキュメントがサンプルSQLの充実しておりテスト用に新たにデータやSQLを作成せずに行けそうですので、これをやってみます。
https://docs.pingcap.com/tidbcloud/vector-search-full-text-search-sql/

まずはインデックスなしでテーブルを作成します。

CREATE TABLE stock_items(
    id INT,
    title TEXT
);
show index from stock_items;

この状態では当たり前ですが、empty setが戻ります。
次に全文検索用INDEXを作成します。

ALTER TABLE stock_items ADD FULLTEXT INDEX (title) WITH PARSER MULTILINGUAL ADD_COLUMNAR_REPLICA_ON_DEMAND;

公式ドキュメントによるとPARSER MULTILINGUALは英語、中国語、日本語、韓国語などに対応しており、一方英語が対象であれば、STANDARDの方が高速に動作する用です。
なお東京リージョンで実行すると未対応ですので、Unsupported FULLTEXT indexエラーとなります。

再度show index from stock_items;を実行すると以下の様に作成されています。

Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression Clustered
stock_items 1 title 1 title A 0 YES BTREE YES NO

Index_typeがBTREEとなっています。これは全文検索用Indexではなく、以下の通常Indexと同じです。

CREATE TABLE stock_items (
    id INT,
    title TEXT,
    -- 普通のインデックス
      INDEX idx_title (title(100))
);

調べたところどうも現時点では表記はそうなっているようですが、公式ドキュメントにも以下が記載されているようにまだまだ発展中の機能ととらえた方がよさそうです。

2025/06/26 更新
PingCapの関口さんから連絡をいただき表示が変わったようです。試すとちゃんとIndex TypeFULLTEXTになっていました!ただしすでに作成済のIndexは表示が変わりませんでした。以下で試している通り機能に遜色はないので気になる方は作り直してください、ぐらいです。

全文検索はまだ初期段階にあり、アクセス範囲が限られています。まだご利用いただけない地域で全文検索をお試しになりたい場合、またはフィードバックやサポートが必要な場合は、お気軽にお問い合わせください。

FTS_MATCH_WORD()

FTS_MATCH_WORD()関数が動作するかどうかでIndexが全文検索用かそうでないか見分けることができます。
公式ドキュメントが用意してくれている以下のテスト用データをInsertします。

INSERT INTO stock_items VALUES (1, "イヤホン bluetooth ワイヤレスイヤホン ");
INSERT INTO stock_items VALUES (2, "完全ワイヤレスイヤホン/ウルトラノイズキャンセリング 2.0 ");
INSERT INTO stock_items VALUES (3, "ワイヤレス ヘッドホン Bluetooth 5.3 65時間再生 ヘッドホン 40mm HD ");
INSERT INTO stock_items VALUES (4, "楽器用 オンイヤーヘッドホン 密閉型【国内正規品】");
INSERT INTO stock_items VALUES (5, "ワイヤレスイヤホン ハイブリッドANC搭載 40dBまでアクティブノイズキャンセル");
INSERT INTO stock_items VALUES (6, "Lightweight Bluetooth Earbuds with 48 Hours Playtime");
INSERT INTO stock_items VALUES (7, "True Wireless Noise Cancelling Earbuds - Compatible with Apple & Android, Built-in Microphone");
INSERT INTO stock_items VALUES (8, "In-Ear Earbud Headphones with Mic, Black");
INSERT INTO stock_items VALUES (9, "Wired Headphones, HD Bass Driven Audio, Lightweight Aluminum Wired in Ear Earbud Headphones");
INSERT INTO stock_items VALUES (10, "LED Light Bar, Music Sync RGB Light Bar, USB Ambient Lamp");
INSERT INTO stock_items VALUES (11, "无线消噪耳机-黑色 手势触控蓝牙降噪 主动降噪头戴式耳机(智能降噪 长久续航)");
INSERT INTO stock_items VALUES (12, "专业版USB7.1声道游戏耳机电竞耳麦头戴式电脑网课办公麦克风带线控");
INSERT INTO stock_items VALUES (13, "投影仪家用智能投影机便携卧室手机投影");
INSERT INTO stock_items VALUES (14, "无线蓝牙耳机超长续航42小时快速充电 流光金属耳机");
INSERT INTO stock_items VALUES (15, "皎月银 国家补贴 心率血氧监测 蓝牙通话 智能手表 男女表");

次に全文検索クエリを実行します。

SELECT * FROM stock_items
    WHERE fts_match_word("bluetoothイヤホン", title)
    ORDER BY fts_match_word("bluetoothイヤホン", title)
    DESC LIMIT 10;

無事動作しました!

id title
1 イヤホン bluetooth ワイヤレスイヤホン
6 Lightweight Bluetooth Earbuds with 48 Hours Playtime
2 完全ワイヤレスイヤホン/ウルトラノイズキャンセリング 2.0
3 ワイヤレス ヘッドホン Bluetooth 5.3 65時間再生 ヘッドホン 40mm HD
5 ワイヤレスイヤホン ハイブリッドANC搭載 40dBまでアクティブノイズキャンセル

どうも完全一致ではなく自然言語モードのあいまい検索が自動で動作しているようです。完全一致で動作させたい場合以下になります。

SELECT * FROM stock_items
WHERE title LIKE '%bluetooth%';

ただしこの場合TiDBの技術特性上KVストアをベースとして動作するのでフルスキャンを行う際のパフォーマンスは気を付ける必要がありそうです。

通常のIndexであれば以下の様になります。

なおMySQLと少し構文が異なります。

SELECT * FROM stock_items
WHERE MATCH(title) AGAINST('bluetoothイヤホン' IN NATURAL LANGUAGE MODE);

全文検索利用時のSQL実行計画が以下です。

"id","estRows","task","access object","operator info"
"TopN_11","10.00","root","","Column#4:desc, offset:0, count:10"
"└─TableReader_27","10.00","root","","MppVersion: 2, data:ExchangeSender_26"
"  └─ExchangeSender_26","10.00","mpp[tiflash]","","ExchangeType: PassThrough"
"    └─TopN_25","10.00","mpp[tiflash]","","Column#4:desc, offset:0, count:10"
"      └─TableFullScan_23","15.00","mpp[tiflash]","table:stock_items","textSearch:(top10 bluetoothイヤホン IN fulltextsearch.stock_items.title)->Column#4, columns: [id, title, _FTS_SCORE], keep order:false, stats:pseudo"

一方Like検索の場合は以下となり使われているストレージが変わっていることがわかりますね。

"id","estRows","task","access object","operator info"
"TableReader_8","12.00","root","","data:Selection_7"
"└─Selection_7","12.00","cop[tikv]","","like(fulltextsearch.stock_items.title, "%bluetooth%", 92)"
"  └─TableFullScan_6","15.00","cop[tikv]","table:stock_items","keep order:false, stats:pseudo"

このあたりの使い分けはこの記事でまとめています。
https://zenn.dev/kameoncloud/articles/dbba3428f4f274

スコア順による並び替え

現時点でfts_match_word関数はマッチしたかしていないかを戻すのみでスコアを戻しません、このため一致順でのデータ操作は直接行えませんがSQLにORDER BY fts_match_word("bluetoothイヤホン", title) DESCを入れることで疑似的にマッチスコア順に並び替えることができるようです。

Discussion