优先使用内置tsvector_update_trigger,它已处理空值、类型转换等边界;需字段加权时才用自定义函数;GIN索引应建在tsv列上以避免重复解析开销。

触发器用 tsvector_update_trigger 还是自定义函数?
直接用内置的 tsvector_update_trigger 更稳妥。它专为同步 tsvector 列设计,已处理空值、类型转换、多字段拼接等边界情况。手写 PL/pgSQL 函数容易漏掉 coalesce() 或权重设置,导致索引内容为空或不一致。
示例:给 articles 表添加自动更新触发器
CREATE TRIGGER tsv_update
BEFORE INSERT OR UPDATE ON articles
FOR EACH ROW
EXECUTE FUNCTION tsvector_update_trigger(
tsv, 'pg_catalog.english', title, body
);
注意三点:
-
tsv是目标tsvector列名,必须已存在 -
'pg_catalog.english'是文本搜索配置,不能写成'english'(会报错) - 后续字段(
title,body)按顺序参与拼接,中间自动加空格
为什么改了文本字段但 tsv 没变?
常见原因是触发器未生效,或字段变更未触发 UPDATE。比如执行 UPDATE articles SET title = title,PostgreSQL 可能跳过实际更新(尤其当 title 值未真正变化时),导致触发器不运行。
验证和修复方法:
- 确认触发器已启用:
SELECT tgname, tgenabled FROM pg_trigger WHERE tgrelid = 'articles'::regclass;,tgenabled应为O - 手动触发一次更新:
UPDATE articles SET title = title || '' WHERE id = 1;,强制触发器执行 - 检查是否用了
UPDATE ... SET col = DEFAULT—— 这种写法不会触发tsvector_update_trigger,需显式赋值
多字段权重不同,还能用内置触发器吗?
不能。tsvector_update_trigger 不支持字段加权,它只是简单拼接。如果标题应比正文权重高,必须换自定义函数。
例如让 title 权重为 A、body 为 B:
CREATE OR REPLACE FUNCTION articles_tsv_trigger()
RETURNS trigger AS $$
BEGIN
NEW.tsv :=
setweight(to_tsvector('pg_catalog.english', COALESCE(NEW.title, '')), 'A') ||
setweight(to_tsvector('pg_catalog.english', COALESCE(NEW.body, '')), 'B');
RETURN NEW;
END
$$ LANGUAGE plpgsql;
关键点:
- 必须用
setweight(..., 'A')显式标注权重,A > B > C > D - 拼接用
||(不是+或字符串连接符) - 触发器定义中要改用
EXECUTE FUNCTION articles_tsv_trigger()
GIN 索引建在列上还是函数表达式上?
两种都行,但行为差异很大:
- 建在
tsv列上:CREATE INDEX idx_articles_tsv ON articles USING GIN(tsv)—— 必须配合触发器保证列值实时,否则查不到新数据 - 建在表达式上:
CREATE INDEX idx_articles_expr ON articles USING GIN(to_tsvector('english', title || ' ' || body))—— 无需额外列和触发器,但每次查询都要重新解析文本,CPU 开销大
生产环境强烈推荐前者。后者只适合低频更新、高读取延迟容忍的场景;一旦表数据量超 10 万行,表达式索引的维护成本和查询抖动会明显上升。
真正容易被忽略的是:GIN 索引对 INSERT/UPDATE 的写放大效应。每条记录变更都会触发索引页分裂,若并发写入频繁,可能成为瓶颈 —— 这时候得考虑批量写入或异步更新策略,而不是只盯着触发器逻辑。










