如何为应用添加搜索:从简单筛选到全文搜索
要为应用添加搜索,先从最简单、可行的方案开始:一个搜索框调用服务端路由,该路由根据查询内容匹配几个字段来筛选数据库中的行,并提供分类或价格等筛选条件。如果这样还不够用,比如需要相关性排序、词干提取或数据量很大,就切换到数据库的全文搜索;只有在需要很强的容错拼写,或在超大数据上即时返回结果时,才考虑托管搜索引擎。无论选哪种方案,搜索都必须遵守权限规则:用户绝不应搜到他们无权查看的记录。
对你的应用而言,“搜索”意味着什么
搜索包含几种不同的功能,大多数应用需要的不止一种:
- **关键词搜索:**一个文本框,例如“查找提到‘花园’的房源”。
- **筛选:**结构化的选项,如分类、城市、价格区间、日期、状态。
- **排序:**最新、最便宜、最相关。
- **自动补全:**输入时给出建议。
- 容错拼写:“restuarant”仍能找到“restaurant”。
对于目录、招聘网站或商店来说,筛选往往比文本框更重要。在选择工具之前,先确定你真正需要哪些功能。
你的选择
| 方式 | 工作原理 | 适用场景 | 局限 |
|---|---|---|---|
| 在浏览器中筛选 | 加载列表,用 JavaScript 过滤 | 小型公开列表(几百条) | 所有数据都发送给每位访客;列表大时变慢 |
| 数据库模式匹配 | 服务端查询“名称包含该文本”,不区分大小写 | 中小型应用、管理后台 | 无排序;大表上可能变慢;没有词干提取 |
| 数据库全文搜索 | Postgres 为单词建立索引并对匹配结果排序 | 大多数内容密集型应用 | 除非添加模糊匹配,否则对拼写错误的容错较弱 |
| 模糊匹配(三元组) | Postgres 比较字符序列的相似度 | 名称、短字段、容错拼写 | 需要扩展和索引;需要调优 |
| 托管搜索引擎 | 独立服务为你的数据建立索引(Algolia、Meilisearch、Typesense、Elasticsearch 等) | 大型目录、即时搜索、强容错拼写 | 另一项需要同步、付费并保障安全的服务 |
如果你的数据已经在 Postgres 中,它内置的全文搜索是很好的折中方案。它会把文本拆分成单词,将单词还原为词干(因此“running”能匹配“run”),忽略常见词,并根据匹配程度对结果排序。配上合适的索引后,即使在大表上也能保持快速。
示例提示词
简单入门:
在房源列表页添加搜索框和筛选条件。搜索应在标题和描述中做不区分大小写的匹配。筛选条件:分类(下拉框)、城市(下拉框)和最高价格。搜索在服务端完成,使用分页的 API 路由,每页 20 条结果。将搜索和筛选条件保存在 URL 中,使结果可以分享,并且浏览器的后退按钮可以正常使用。没有匹配结果时,显示友好的空状态提示。
升级为全文搜索:
将房源搜索切换为 Postgres 全文搜索,对标题(权重更高)和描述进行索引。添加正确的索引,按相关性排序,并保留现有的筛选和分页功能。不要修改任何现有数据。
为名称添加容错拼写:
为企业名称添加模糊匹配,使小的拼写错误也能找到结果。如果可用,使用三元组相似度并建立索引;如果不可用,则回退到当前的搜索方式。
分步操作
- **列出用户会搜索什么。**需要搜索哪些字段(标题、描述、标签、名称)?有哪些筛选条件?如果可以,请询问几位真实用户。
- **从服务端开始。**添加一个 API 路由,接收查询内容和筛选条件,并返回一页结果。不要把所有数据加载到浏览器中。
- **在查询中应用权限。**如果用户只能看到自己的记录,或只能看到已发布的记录,这个条件应与搜索放在同一个查询中。
- **从一开始就加入分页。**可以使用页码,也可以使用“加载更多”。
- **将搜索状态放入 URL。**例如
?q=garden&city=leeds。这样链接可以分享,后退按钮也能正常工作,如果希望搜索引擎抓取筛选后的页面,它们也能被抓取。 - **输入时对搜索做防抖处理。**停止输入后等待几百毫秒再发起查询,避免每次按键都发送一次请求。
- 在确定了要筛选和搜索的列之后,再添加索引。
- **逐步升级:**先全文搜索,再模糊匹配,最后才是托管搜索引擎,并且只有在较简单的方案无法满足真实用户需求时才升级。
- **用接近真实的数据进行测试:**数百或数千行数据、特殊字符、空查询、超长查询,以及如果你支持其他语言,也要测试这些语言的内容。
常见错误
- **把整张表加载到浏览器中。**50 条数据时没问题,5 万条数据时就很痛苦,而且会暴露每一条记录,包括用户本不应看到的记录。
- **搜索忽略权限。**搜索是常见的数据泄露点:查询中忘记加上“仅限本人”或“仅限已发布”的条件,导致私密或草稿记录出现在结果中。
- **用原始用户输入拼接 SQL。**务必使用参数化查询。AI 构建工具通常会这样做,但如果你看到字符串被拼接在一起,请检查一下。
- 没有空状态。“没有结果”并附上建议(清除筛选条件、检查拼写),能避免用户直接离开。
- **没有索引。**用测试数据时搜索感觉正常,但随着真实数据增长会越来越慢。
- **直接上搜索服务。**这样你就要维护第二份数据副本,处理删除操作,并保护另一组密钥,而且往往看不出有什么明显收益。
- **忘记移动端。**筛选条件需要能在手机上显示;使用抽屉式或可折叠的筛选面板,通常比侧边栏更合适。
检查清单
- 你知道哪些字段需要搜索,以及有哪些筛选条件
- 搜索在服务端运行,并返回分页结果
- 查询遵循应用其他部分相同的权限规则
- 用户输入作为参数传递,绝不拼接进 SQL
- 搜索状态保存在 URL 中
- 输入过程已做防抖处理
- 用于搜索和筛选的列已建立索引
- 空状态、无结果状态和错误状态都清晰明确
- 已用接近真实的数据量和杂乱的输入进行测试
在 Mythex 中添加搜索
在基于 Mythex 构建的应用中,数据通常存放在项目自己的 Postgres 数据库中,因此你可以直接使用上面的提示词:先做模式匹配,需要排序时再使用 Postgres 全文搜索。如果你想使用托管搜索引擎,请使用你自己的账户和密钥,将其保存为项目机密,并让代理在服务端完成索引和查询,具体步骤参见集成任意 API。
搜索是许多应用的核心:有关它如何融入整个产品,请参见如何搭建目录网站、如何搭建招聘网站和如何搭建市场平台。
常见问题
为应用添加搜索,最简单的方法是什么?
如果数据存放在数据库中,那么最容易上手的方法是:服务端路由对几个字段做不区分大小写的匹配,筛选出符合条件的行。如果是已经加载到浏览器里的小列表,直接在浏览器中筛选就更简单。
搜索一定要用 Algolia 或 Elasticsearch 吗?
一开始通常不需要。Postgres 全文搜索就能很好地处理单词匹配、排序,以及从几千到几百万行的数据。只有在需要很强的容错拼写、输入时即时显示结果(数据量很大时),或需要精细的相关性调优时,托管搜索引擎才值得使用。
为什么我的搜索在输入错字时找不到结果?
基础匹配和标准全文搜索查找的是原样输入的单词(或其词干),所以拼写错误就匹配不上。你需要模糊匹配,例如 Postgres 中的三元组相似度,或者支持容错拼写的搜索引擎。
搜索应该在浏览器中运行,还是在服务端运行?
除非是很小的公开列表,否则应放在服务端。服务端搜索可以扩展,能遵守权限规则,也不会把整个数据集发送给每位访客。