最简路径是pgx原生执行SQL,需确保zhparser已安装、ts_config名称匹配、GIN索引指定相同配置,否则中文搜索静默失败。

Go里用pgx调用tsvector和@@操作符最简路径
不用GORM、不写触发器,pgx原生执行全文查询是最快上手方式。关键不是“能不能查”,而是“怎么避免字段没索引、配置没生效、中文搜不到”这三类静默失败。
常见错误现象:SELECT * FROM articles WHERE to_tsvector('chinese', content) @@ plainto_tsquery('chinese', 'Go'); 返回空结果,但数据明明存在——大概率是没装zhparser扩展,或配置名写成chinese但实际创建的是chinese_zh。
- 先确认分词配置是否存在:
SELECT cfgname FROM pg_ts_config;,中文必须看到类似chinese_zh的条目 - 建GIN索引必须显式指定配置:
CREATE INDEX idx_articles_content ON articles USING GIN (to_tsvector('chinese_zh', content));,漏掉'chinese_zh'参数就退化成单字切分 -
pgx中直接拼SQL即可,无需额外类型注册:rows, err := conn.Query(ctx, "SELECT id, title FROM articles WHERE to_tsvector($1, content) @@ plainto_tsquery($1, $2)", "chinese_zh", keyword)
GORM中绕过Where限制用Raw执行MATCH查询
GORM的Where()完全不识别@@或MATCH语法,硬套会报near "@@": syntax error。必须用Raw(),且不能依赖Scan()自动映射大文本字段。
使用场景:已有Article模型,想加全文搜索但不想改结构体定义。
立即学习“go语言免费学习笔记(深入)”;
- 只查必要字段:
db.Raw("SELECT id, title, ts_rank(to_tsvector(?, content), plainto_tsquery(?, ?)) AS score FROM articles WHERE to_tsvector(?, content) @@ plainto_tsquery(?, ?) ORDER BY score DESC", cfg, kw, kw, cfg, kw).Scan(&results) - 注意
ts_rank()返回浮点数,结构体里对应字段得是float64,否则Scan会静默丢值 - 别在
Raw()里用SELECT *——content字段可能几MB,拖慢整行传输,尤其高并发时
中文分词失效的三个真实检查点
不是“装了zhparser就自动好使”,中文搜不到基本卡在这三步中的某一个。
错误现象:ts_debug('chinese_zh', 'Go语言')返回go和语言两个独立token,而不是go语言或go——说明分词器没把英文+中文当整体处理。
- 确认
zhparser已启用:CREATE EXTENSION IF NOT EXISTS zhparser;,这条必须在目标数据库里单独执行,不是连接时自动加载 - 检查text search configuration是否绑定正确:
SELECT * FROM pg_ts_config_map WHERE mapcfg = (SELECT oid FROM pg_ts_config WHERE cfgname = 'chinese_zh');,若无结果说明映射没做 - 自定义词典未生效?
zhparser默认词典不含编程术语,需手动往dict_xxx.txt加go语言 100这类行,再zhparser_reload_dict()
性能陷阱:tsvector字段该不该存、怎么存
有人把to_tsvector('chinese_zh', title || ' ' || content)结果存进表里一列,看似省CPU,实则引入一致性风险和存储膨胀。
真正影响性能的是GIN索引是否覆盖查询字段,不是tsvector要不要落盘。
- 小数据量(WHERE to_tsvector(...) @@ ...,不存tsvector字段,避免维护成本
- 中等数据量(5k–10w行):用触发器自动更新tsvector列,比应用层批量UPDATE更可靠;但注意触发器函数里
COALESCE(title, '')必须写全,空值会导致整个向量为NULL - 大文本字段参与拼接时,
||操作可能截断——PostgreSQL默认text拼接上限是1GB,但实际到几MB就明显变慢,建议限制content前10000字符:substring(content for 10000)
最易被忽略的是分词配置与查询配置必须严格一致。同一个chinese_zh配置,建索引时用了它,查询时却传simple,结果永远为空——这种错不会报错,只会默默不匹配。


















