这道题问的是[[RAG]]底层知识库的数据存储方案,其实本质是在问:你怎么在语义匹配和精确过滤之间找到平衡。不是让你在关系库和[[向量库]]里二选一,真正落地往往是分层存储加[[混合检索]],向量负责语义,结构化负责精确,两条腿走路。
先说边界划分。结构化数据库,比如PostgreSQL,擅长精确查询、权限过滤、业务字段检索,比如查某个订单号、某个时间范围、某个用户等级,这些它天生强项。向量数据库,比如Milvus或pgvector,擅长语义相似度,比如搜“退款政策”能匹配到“退货流程”。但两者不是替代关系,举个例子,客服退款场景里,用户问“我买的东西坏了怎么退”,你不能只靠向量把相关文档捞出来,还得过滤掉非当前用户的订单、只展示有效售后政策,这就是结构化过滤。所以我的思路是:向量做粗召回,结构化做精过滤,两者结合才能避免召回结果里一堆不相关的。
具体到存储方案,我通常看规模。如果数据量百万级以内,团队也小,我倾向用PostgreSQL加pgvector插件,统一存储。优势是事务一致,运维简单,一条SQL就能同时做向量检索和业务过滤,比如SELECT * FROM docs WHERE category='售后' ORDER BY [[embedding]] <=> '[0.1,0.2,0.3]' LIMIT 10。但有个前提:你的向量维度不能太高,并发量也别太大,否则pgvector性能会崩。如果数据千万级以上,或者需要高并发低延迟,我会选分离架构,比如Milvus做向量库,MySQL做关系库,中间用doc_id关联。这里有个坑:向量库没有事务,双写时如果关系库写成功但向量库写失败,数据就不一致了。所以我会用异步任务加对账机制,而不是强事务。写入时先写关系库,状态标记为“待处理”,后台任务生成embedding再写向量库,写成功后更新状态。定期跑对账脚本,把状态不一致的补写或告警。
检索流程上,我一般走多路召回:向量检索Top-K,同时用[[BM25]]做关键词召回,结果合并后去重,再做[[Rerank]],最后用结构化条件过滤。比如用户搜“满200减50”,向量可能把“满300减80”也召回来,但BM25能精准命中“满减”关键词,合并后rerank把最相关的排前面,再过滤掉已过期的活动。这里有个常见失败场景:如果只依赖向量,当用户输入的是精确术语比如订单号“ORD123456”,向量检索基本废掉,因为embedding对精确匹配不敏感。所以关键词召回是必选项,不是可选项。
最后提一个我特别关注的点:数据一致性。分离架构下,向量库和关系库的同步延迟怎么控制?我见过上线后因为同步不及时,用户改了文档内容但向量还是旧的,导致召回结果全是过时信息。所以除了异步对账,我还会加版本号机制,每次检索带上版本号,如果向量库版本落后,就降级走关键词搜索或直接报错。这个对账和版本控制的细节,我觉得是生产环境最容易踩坑的地方。
所以总结一下,我更倾向把知识库存储看成分层设计:底层用关系库存原始数据和元数据,上层用向量库做语义索引,中间用异步管道保持最终一致。选型上不迷信专用库,也不排斥一体化方案,关键看你的数据规模、查询模式和团队运维能力。