先别急着决定建几种索引。Hologres 上向量列和文本列能不能各走索引、走哪种索引,取决于实例版本和已安装的扩展。比较稳的路径是:先确认实例实际可用的索引类型,再分别给文本侧和向量侧各写一条只走单侧条件的召回基线,拿到条数与耗时,最后比对两组结果的重合度,再决定要不要合并。
适用场景:同一张表既有向量列又有文本描述列,需要决定建哪种索引、条件怎么写。操作动作:查系统表或直接试建索引,记录可用类型;分别跑文本侧与向量侧单条件查询,记录返回条数和耗时;按主键求交集算重合度。验证方式:以实例返回结果和 EXPLAIN 输出为准。风险边界:索引类型、距离函数写法、维度上限都可能随版本变化,下面的骨架需要按实际环境替换后再用。
先确认当前实例上能建哪些索引类型
不要靠印象判断。先连上实例,查系统表看当前支持哪些索引访问方法,以及向量相关扩展是否已经装上:
SELECT amname FROM pg_am ORDER BY amname;
SELECT extname, extversion FROM pg_extension;
SELECT indexname, indexdef FROM pg_indexes WHERE tablename = '你的表名';
如果向量索引依赖某个扩展,pg_extension 里能看到它装没装、版本是多少。再看某个已建索引用的是哪种访问方法,可以用 psql 的 \d+ 表名 直接看索引定义。
另一种确认办法是直接写建索引语句,观察返回是报错、提示不支持,还是正常创建。把每次尝试的语句原文、返回信息、是否成功记成一张小表,后面选型以这份记录为准,而不是凭记忆。要提醒的是:具体能建哪些索引类型,以你的实例支持为准,不同版本的向量索引名称、参数写法、支持的距离函数都可能不一样,照搬别处的建索引语句不一定能跑通。
把混合字段拆成文本侧和向量侧两列
让两类召回各自有独立、可单独测试的字段,是后面做基线对比的前提。字段命名建议一眼能看出用途,例如 content_text 放原始描述文本,embedding 放向量。维度不要写死,按你用的模型输出填。
CREATE TABLE IF NOT EXISTS item_search (
id bigint,
content_text text, -- 文本侧:供关键词或相关性条件使用
embedding real[] -- 向量侧:写入时由外部模型生成
-- 若实例要求固定维度,可改为对应向量类型并写明维度
);
写入时文本列直接写原文,向量列写模型输出的数组或对应类型值,两者来自同一条记录,保证后面能按 id 对齐。维度、类型名、是否需要显式 CAST,都以实例支持的写法为准;先插几行样例数据,确认能正常读出来再继续。
先写一条只走文本条件的查询
这条不要带任何向量排序,只留文本侧条件,目的是拿到文本侧单独的召回基线:
SELECT id, content_text
FROM item_search
WHERE content_text LIKE '%关键词%' -- 或实例支持的全文检索条件
LIMIT 50;
记录方式:在 psql 里先开 \timing,或者对同一条语句跑 EXPLAIN (ANALYZE, BUFFERS),把返回条数和耗时记下来。如果实际命中远多于 LIMIT,说明文本条件本身不紧,后面算重合度时要注意 LIMIT 已经截断了结果。
再写一条只走向量排序的查询
这条只做向量距离排序,不带文本过滤,用来拿向量侧单独的召回基线:
SELECT id,
embedding <=> '[....]' AS dist -- 距离运算符以实例支持为准
FROM item_search
ORDER BY dist
LIMIT 50;
距离函数和运算符(L2、内积、余弦各自的写法)必须以实例支持为准,不要虚构不存在的接口能力;如果实例只在某类索引下才能做近似检索,写法可能还要配合额外设置。同样记录返回条数和耗时,最好和文本侧用同一个 LIMIT,便于比较。
比对两组结果的重合度再决定是否合并
把上面两条查询各自的结果放进 CTE,按 id 求交集,并顺便记录各自条数:
WITH t AS (
SELECT id FROM item_search WHERE content_text LIKE '%关键词%' LIMIT 50
), v AS (
SELECT id FROM item_search ORDER BY embedding <=> '[....]' LIMIT 50
)
SELECT
(SELECT count(*) FROM t JOIN v USING (id)) AS both_cnt,
(SELECT count(*) FROM t) AS t_cnt,
(SELECT count(*) FROM v) AS v_cnt;
如果交集占比很高,说明两种召回拿到的结果高度相似,合并意义有限,可以先只保留其中一种索引,少一份维护成本;交集很低甚至几乎没有,才值得把两类条件放进同一条查询。这个判断要结合业务对召回覆盖的要求,不能只看一个比例。
需要合并时,通用写法是把文本条件放进 WHERE,向量排序放进 ORDER BY,并注意 LIMIT 的截断顺序——先过滤后排序的情况下,文本条件过紧会让参与向量排序的候选集变得很小:
SELECT id, content_text
FROM item_search
WHERE content_text LIKE '%关键词%'
ORDER BY embedding <=> '[....]'
LIMIT 50;
跑完这条合并查询后,再把它的 id 集合分别和文本侧、向量侧的基线结果比一次,确认合并确实带来了新的命中,而不是只把两边已有的结果重新排了序。如果合并后条数明显变少,可以先放宽文本条件或适当提高 LIMIT,具体取值结合实例开销和返回结果再调。