gorm postgres全文搜索
在 GORM 中使用 PostgreSQL 全文搜索,核心思路是利用 GORM 的 Raw() 或 Where() 配合原生 SQL 表达式,调用 PostgreSQL 的 tsvector、tsquery 和 @@ 操作符。
以下是完整的实现方案:
---
1. 表结构与迁移
在模型中定义一个 tsvector 类型的搜索列,并通过 GORM 迁移创建:
type Article struct {
ID uint `gorm:"primaryKey"`
Title string `gorm:"type:text;not null"`
Content string `gorm:"type:text"`
// tsvector 列,用于全文搜索
SearchVec string `gorm:"type:tsvector;index:idx_articles_search,type:gin"`
CreatedAt time.Time
}
// AutoMigrate 会自动创建表和 GIN 索引
db.AutoMigrate(&Article{})
> 注意:GORM 的 index tag 支持指定索引类型为 gin,这对全文搜索性能至关重要。
---
2. 填充 tsvector 数据
方式一:应用层手动更新(适合数据量小或初始化)
// 将 Title 和 Content 合并转换为 tsvector
db.Exec(`
UPDATE articles
SET search_vec = to_tsvector('chinese', COALESCE(title, '') || ' ' || COALESCE(content, ''))
`)
方式二:数据库触发器自动维护(推荐)
通过 GORM 的 Migrator().RunWithValue() 或 Exec() 创建触发器,确保数据变更时自动更新 tsvector:
// 创建触发器函数
db.Exec(`
CREATE OR REPLACE FUNCTION articles_search_update() RETURNS trigger AS $$
BEGIN
NEW.search_vec := to_tsvector('chinese',
COALESCE(NEW.title, '') || ' ' || COALESCE(NEW.content, '')
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`)
// 绑定触发器
db.Exec(`
CREATE TRIGGER trigger_articles_search_update
BEFORE INSERT OR UPDATE ON articles
FOR EACH ROW EXECUTE FUNCTION articles_search_update();
`)
> 触发器方案能确保搜索索引始终与源数据同步,是生产环境的标准做法。
---
3. 执行全文搜索查询
基础搜索(简单匹配)
keyword := "gorm 教程"
var articles []Article
db.Where("search_vec @@ plainto_tsquery('chinese', ?)", keyword).Find(&articles)
plainto_tsquery 会将用户输入的纯文本自动转换为 tsquery,无需手动处理布尔运算符。
带相关性排序的搜索
keyword := "gorm 教程"
var results []struct {
Article
Rank float64 `gorm:"column:rank"`
}
db.Raw(`
SELECT *, ts_rank(search_vec, plainto_tsquery('chinese', ?)) as rank
FROM articles
WHERE search_vec @@ plainto_tsquery('chinese', ?)
ORDER BY rank DESC
`, keyword, keyword).Scan(&results)
ts_rank() 会根据词频和位置计算相关性得分,实现类似搜索引擎的排序效果。
多字段加权搜索(标题权重更高)
如果需要让标题匹配的结果排在前面,可以使用 setweight:
// 首先修改触发器,给不同字段设置权重
db.Exec(`
CREATE OR REPLACE FUNCTION articles_search_update() RETURNS trigger AS $$
BEGIN
NEW.search_vec :=
setweight(to_tsvector('chinese', COALESCE(NEW.title, '')), 'A') ||
setweight(to_tsvector('chinese', COALESCE(NEW.content, '')), 'B');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`)
查询时使用 ts_rank() 会自动考虑权重:
db.Raw(`
SELECT *, ts_rank(search_vec, plainto_tsquery('chinese', ?)) as rank
FROM articles
WHERE search_vec @@ plainto_tsquery('chinese', ?)
ORDER BY rank DESC
LIMIT 20
`, keyword, keyword).Scan(&results)
权重等级为 A(1.0) > B(0.4) > C(0.2) > D(0.1),标题命中时会获得更高排名。
支持网页搜索语法(推荐用于用户输入)
websearch_to_tsquery 支持类似 Google 的搜索语法:OR、-排除、""短语:
// 用户输入: "gorm OR postgres -mysql"
keyword := "gorm OR postgres -mysql"
db.Where("search_vec @@ websearch_to_tsquery('chinese', ?)", keyword).Find(&articles)
这比 plainto_tsquery 更灵活,适合暴露给终端用户的搜索框。
---
4. 搜索结果高亮
使用 ts_headline() 生成带高亮标记的摘要:
db.Raw(`
SELECT
id,
title,
ts_headline('chinese', content,
plainto_tsquery('chinese', ?),
'StartSel=<mark>, StopSel=</mark>, MaxWords=50, MinWords=10'
) as snippet
FROM articles
WHERE search_vec @@ plainto_tsquery('chinese', ?)
`, keyword, keyword).Scan(&results)
这会返回匹配关键词被 <mark> 标签包裹的文本片段,方便前端直接展示。
---
5. 前缀搜索(自动补全)
// 搜索以 "post" 开头的词
prefix := "post"
db.Where("search_vec @@ to_tsquery('chinese', ? || ':*')", prefix).Find(&articles)
:* 是前缀匹配运算符,适合实现搜索建议功能。
---
6. 中文支持注意事项
PostgreSQL 默认的全文搜索配置对中文支持有限,建议:
1. 使用 simple 配置:对中文不做词干提取,按空格分词to_tsvector('simple', title) // 中文内容推荐用 simple
2. 安装额外扩展:如 pg_jieba 或 zhparser 实现中文分词CREATE EXTENSION pg_jieba;
-- 然后使用 'jiebacfg' 作为配置名
3. 混合语言内容:可以存储多语言 tsvector 或统一使用 simple 配置。
---
完整示例代码
package main
import (
"fmt"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Article struct {
ID uint `gorm:"primaryKey"`
Title string `gorm:"type:text;not null"`
Content string `gorm:"type:text"`
SearchVec string `gorm:"type:tsvector;index:idx_articles_search,type:gin"`
CreatedAt time.Time
}
func main() {
dsn := "host=localhost user=postgres password=postgres dbname=test port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// 自动迁移
db.AutoMigrate(&Article{})
// 创建触发器(只需执行一次)
db.Exec(`
CREATE OR REPLACE FUNCTION articles_search_update() RETURNS trigger AS $$
BEGIN
NEW.search_vec :=
setweight(to_tsvector('simple', COALESCE(NEW.title, '')), 'A') ||
setweight(to_tsvector('simple', COALESCE(NEW.content, '')), 'B');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`)
db.Exec(`
DROP TRIGGER IF EXISTS trigger_articles_search_update ON articles;
CREATE TRIGGER trigger_articles_search_update
BEFORE INSERT OR UPDATE ON articles
FOR EACH ROW EXECUTE FUNCTION articles_search_update();
`)
// 插入测试数据
db.Create(&Article{Title: "GORM 入门教程", Content: "GORM 是 Go 语言优秀的 ORM 框架"})
db.Create(&Article{Title: "PostgreSQL 全文搜索", Content: "使用 tsvector 实现高效搜索"})
// 执行搜索
keyword := "GORM 教程"
var articles []Article
result := db.Where(
"search_vec @@ websearch_to_tsquery('simple', ?)",
keyword,
).Find(&articles)
if result.Error != nil {
panic(result.Error)
}
for _, a := range articles {
fmt.Printf("ID: %d, Title: %s\n", a.ID, a.Title)
}
}
---
关键点总结
功能 GORM 实现方式 PostgreSQL 函数
文本转索引 tsvector 列 + GIN 索引 to_tsvector()
自动同步 数据库触发器 BEFORE INSERT OR UPDATE
用户搜索 Where("... @@ plainto_tsquery(...)") plainto_tsquery / websearch_to_tsquery
相关性排序 Raw() + ts_rank() ts_rank() / ts_rank_cd()
结果高亮 Raw() + ts_headline() ts_headline()
前缀匹配 to_tsquery('prefix:*') :* 运算符
这套方案充分利用了 PostgreSQL 原生全文搜索能力,通过 GORM 的 SQL 注入安全查询接口实现,性能上 GIN 索引可以支持百万级数据毫秒级响应。
