你一定被原生 IndexedDB 的查询限制气笑过:它只支持前缀匹配 (IDBKeyRange.bound),想搜"中间字符"?对不起,原生没有 LIKE %keyword%。
在 AI Prompt Manager 场景下,用户可能搜"前端"、"报告"、"审计",这些词可能出现在标题中间。如果直接 getAll() 拿出来在 JS 里 filter,数据量上万时,内存占用 和主线程卡顿会直接让你的"资深"头衔蒙尘。
1. 为什么原生查询不行?
原生 IndexedDB 的索引是 B-Tree 结构,它能极快地找到"以某字符开头"的数据,但无法处理"包含某字符"。
- 低级做法 :
getAll()+Array.prototype.filter。数据量 10k+ 时,解析 JSON 的开销会让 UI 瞬间掉帧。 - 高级做法 :倒排索引(Inverted Index) 。
2. 方案一:引入 FlexSearch (极致性能的首选)
FlexSearch 是目前 Web 端最快的全量搜索库,它的速度比 Fuse.js 快一个数量级。
实战集成:PromptDB + FlexSearch
JavaScript
javascript
import { Index } from "flexsearch";
class SearchablePromptDB extends PromptDB {
constructor() {
super();
// 创建内存索引,开启"模糊匹配"(suggest)
this.index = new Index({
tokenize: "forward", // 适合前缀+中间搜索
resolution: 9,
cache: true
});
}
// 1. 同步索引:在数据存入 DB 的同时,存入 FlexSearch
async addWithIndex(prompt) {
await this.set(prompt);
this.index.add(prompt.id, `${prompt.title} ${prompt.content}`);
}
// 2. 毫秒级搜索
search(query) {
const results = this.index.search(query, { limit: 20 });
// results 返回的是 id 数组,再去 DB 拿具体对象(或从内存缓存拿)
return results;
}
}
3. 方案二:手写"倒排索引" (不依赖库的深度方案)
如果你不想增加包体积,可以利用 IndexedDB 的 multiEntry 特性。
核心技巧:词根索引化
将 Prompt 的标题和标签拆分成字/词,存入一个专门的 searchTerms 数组字段。
JavaScript
scss
// 存入时
const prompt = {
id: 'p1',
title: '金融审计助手',
// 手动分词:['金', '融', '审', '计', '助', '手', '金融', '审计']
searchKeywords: splitWords('金融审计助手')
};
// 在 DB 初始化时,为这个数组字段开启 multiEntry
store.createIndex('keywords_idx', 'searchKeywords', { multiEntry: true });
// 查询时
const range = IDBKeyRange.only('审计');
const request = index.getAll(range); // 瞬发响应,因为它是原生索引
注:multiEntry: true 会为数组中的每个元素在 B-Tree 中创建一个独立的指针。
4. 性能瓶颈的终极解决方案:Web Worker
即便搜索算法再快,当数据量达到 10 万级,字符串分词 和索引构建依然会占用主线程。
架构设计:将搜索推入边缘
- 主线程:只负责接收用户输入和渲染结果。
- Worker 线程:持有 FlexSearch 实例,监听 IndexedDB 的变化。
- 流程:输入 -> PostMessage -> Worker 搜索 -> 返回 ID 列表 -> 主线程渲染。
JavaScript
ini
// search.worker.js
self.onmessage = ({ data: { type, payload } }) => {
if (type === 'SEARCH') {
const results = flexIndex.search(payload);
self.postMessage(results);
}
};
5. 优化建议
- 分词策略 :中文搜索最简单有效的是 "二元分词" 。例如"我的代码",拆分为
['我', '的', '代', '码', '我的', '的代码']。 - 防抖 (Debounce) :搜索框必须加 150-300ms 防抖,避免用户打字太快导致 Worker 任务堆积。
- 结果高亮 :搜索是毫秒级的,但千万别忘了在 UI 上用
<mark>标签高亮匹配字符,这才是"体感"流畅的关键。 - 分片加载:搜索结果如果太多,只取前 20 条,配合滚动加载。