每次看到那些几万甚至几十万数据量的小项目,架构图里赫然画着一个 Elasticsearch (ES) 集群,我就脑壳疼。大厂天天吹嘘 ES 应对 PB 级数据的检索性能,可他们没告诉你小团队为了维护这个“黑盒”要付出多大的心智负担。JVM 调优、内存溢出(OOM)、索引映射变动(Mapping Changes)导致的重建、主从同步延迟,外加两台服务器打底的内存开销,对于一个小团队来说,简直就是高射炮打蚊子,折腾得鸡飞狗跳,最后业务还没上线,机器费用先顶不住了。
如果你的数据量在百万级别以内,且只是做简单的多字段站内模糊搜索,干脆直接用 PostgreSQL (PG) 的 tsvector 和 GIN 索引。PG 作为一个关系型数据库,不仅省去了双写一致性的硬编码烦恼,直接在同一个事务里就能保证数据的一致性,而且查询性能在毫秒级,稳得一匹。
这里直接给出一套在 PostgreSQL 里实现高效全文检索的实战 SQL 建表和查询方案。我们创建一个 articles 表,通过一个生成的隐藏列 search_vector 自动合并标题和内容,并绑定 GIN 索引。
-- 1. 创建文章表,包含标题、内容以及用于全文检索的 tsvector 字段
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
status VARCHAR(50) DEFAULT 'draft',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 2. 结合 title 和 content 字段,生成一个 tsvector 生成列 (Generated Column)
-- 使用 'simple' 配置进行分词(对于英文或已分词的中文非常适用)
ALTER TABLE articles
ADD COLUMN search_vector tsvector GENERATED ALWAYS AS (
setweight(to_tsvector('simple', COALESCE(title, '')), 'A') ||
setweight(to_tsvector('simple', COALESCE(content, '')), 'B')
) STORED;
-- 3. 在 search_vector 上创建 GIN (Generalized Inverted Index) 倒排索引
CREATE INDEX idx_articles_search_vector ON articles USING gin(search_vector);
-- 4. 模拟写入测试数据
INSERT INTO articles (title, content, status) VALUES
('如何优化 PostgreSQL 性能', '这篇文章详细介绍了关于 GIN 索引、B-Tree 索引以及查询规划器的配置参数调优。', 'published'),
('React 19 渲染机制解析', 'React 19 引入了 Server Actions 和新的水合机制,大大减轻了前端的心智负担。', 'published'),
('Docker 容器安全最佳实践', '确保基础镜像安全,避免使用 root 用户运行容器,并且定期扫描漏洞。', 'published');
-- 5. 高性能搜索查询示例:搜索包含 "PostgreSQL" 或 "索引" 的文章,并按匹配权重排序
SELECT
id,
title,
ts_rank_cd(search_vector, query) as rank
FROM
articles,
to_tsquery('simple', 'PostgreSQL | 索引') query
WHERE
search_vector @@ query
AND status = 'published'
ORDER BY
rank DESC,
created_at DESC
LIMIT 10;
对于中文分词,如果你不想折腾底层的分词插件,可以直接利用 PG 的 zhparser 插件,或者在应用层写入数据时自己用 Node/Go 的分词库分好词,用空格拼成字符串,再存入 PG。通过 setweight 我们可以把标题的匹配权重设为 ‘A’,内容设为 ‘B’,这样搜出来的结果排序非常符合人类的直觉。
我们在线上跑着一个 80 万行数据的问答社区,用这套 PG 全文检索方案,配合 GIN 索引,搜索接口的响应时间稳定在 15ms 左右,服务器内存只占了不到 500MB。要是换成 ES,光是跑起 Java 虚拟机 and 同步工具 Logstash 估计就能把配置最低的云服务器直接撑爆。
架构设计最忌讳的就是盲目跟风引入复杂中间件。你们在做小项目搜索时,有没有遇到过非要上 Elasticsearch 的老板或者同事?怎么说服他们的?