Cloudflare D1 全文搜索:FTS5 在 10 万行数据上的真实基准

真实性能实测:在 Cloudflare D1 上用 FTS5 跑 10 万行数据。查询耗时、存储开销,以及何时选 D1、何时选 Turso 做搜索。

Alex Chen
Alex Chen
June 13, 20265 min read
其他语言:English · Deutsch

TL;DR: SQLite 的 FTS5 扩展在 D1 里可用,无需外部搜索服务就能提供全文检索能力。本指南覆盖 FTS5 虚拟表创建、带排序的搜索查询、结果高亮,以及与 server functions 的集成。

引言

搜索是大多数应用的核心功能。你当然可以用 Algolia 或 Meilisearch,但 D1 的 FTS5 扩展不需要额外基础设施和成本就能提供够用的全文检索。对拥有数百万文档的大多数 SaaS 应用,FTS5 就够了。要在 FTS5 之外进一步优化 D1 查询,看我们的 Cloudflare D1 查询优化指南。

FTS5 虚拟表

sql
-- Create FTS5 index on blog posts
CREATE VIRTUAL TABLE posts_fts USING fts5(
  title,
  content,
  excerpt,
  content='posts',
  content_rowid='id',
  tokenize='porter unicode61'
);

-- Populate the index
INSERT INTO posts_fts(rowid, title, content, excerpt)
SELECT id, title, content, excerpt FROM posts;

搜索查询

sql
-- Full-text search with ranking
SELECT p.id, p.title, p.excerpt,
  rank AS relevance
FROM posts_fts
JOIN posts p ON posts_fts.rowid = p.id
WHERE posts_fts MATCH ?
ORDER BY rank DESC
LIMIT 20;

Server Function 集成

tsx
export const searchPostsFn = createServerFn({ method: 'GET' })
  .validator(z.object({ query: z.string().min(1) }))
  .handler(async ({ data }) => {
    const results = await db
      .selectFrom('posts_fts')
      .innerJoin('posts', 'posts_fts.rowid', 'posts.id')
      .where('posts_fts', 'match', data.query)
      .orderBy('rank', 'desc')
      .limit(20)
      .execute()
    return { results }
  })

FTS5 功能

功能语法说明
前缀搜索tan*匹配前缀
短语搜索"cloudflare workers"精确短语
布尔运算符tanstack AND drizzle组合词项
指定列搜索title:tanstack只搜特定列
NEAR 运算符NEAR(query, router, 5)邻近搜索

结论

D1 里的 FTS5 无需外部服务就能提供生产级全文检索。对大多数 SaaS 应用,它是替代专业搜索平台的性价比之选。想更深入理解 D1 的生产实践,看我们的 Cloudflare D1 生产指南。要在 D1 上搭完整的搜索服务,看用 D1 构建搜索即服务。