前阵子我们组在规划一个新项目的存储方案需求一句话就能说清业务数据一直在 PolarDB-X 里现在要基于大模型做语义检索向量从哪来、放哪去、怎么查成了争论最久的问题。有人提议上一套独立的向量数据库有人觉得用 Redis 的向量模块凑合最后我们认真评估了 PolarDB-X 关系型数据库对向量检索的原生支持能力决定把向量和业务数据放在同一个库里。这个决策过程踩了不少坑也推翻了不少固有认知。这篇就结合实际选型和压测经历聊聊 PolarDB-X 一体化向量检索方案到底适合谁、怎么用、哪些坑必须提前知道。1. 为什么非要把向量检索塞进关系型数据库1.1 业务数据与向量数据的割裂是当前最大痛点做 RAG检索增强生成或者相似图片搜索的团队基本都会遇到同一个问题业务数据存在 MySQL、PolarDB-X 这类关系型数据库里而向量数据通常被认为应该放到独立的向量数据库里。于是架构演变成了这样业务库负责存商品、订单、用户信息向量库负责存 embedding中间靠一堆同步任务维持两边数据的一致性。听起来很合理但真落地起来就是灾难。商品标题改了向量库里的旧向量还在用户删除了一条记录向量库里对应的 embedding 成了孤儿每天凌晨的同步任务偶尔失败查出来的结果跟线上业务状态对不上。我们在做知识库问答的时候就深有体会——用户问我的订单为什么被取消了这个查询既涉及订单表的状态字段又要用向量检索去匹配相似的语义两边数据晚同步一分钟答案就是错的。1.2 两套库带来的技术代价远超想象独立向量数据库不是不能用而是你得先接受一系列额外成本运维成本直接翻倍。监控、备份、扩容、权限管理每套系统都要单独做一套。数据一致性靠应用层保证。你要自己写双写逻辑或者上同步中间件一旦出现网络分区或消息积压数据就对不齐了。事务变成了奢望。关系型数据库里一个事务修改了业务字段向量库那边的更新独立于这个事务要么补尝要么接受终不一致。查询变复杂。业务过滤条件比如只看上架商品在向量库里很难表达通常只能把向量先查出来再用商品 ID 去业务库二次过滤响应时间白白多一跳。PolarDB-X 原生支持向量检索后这些问题一下子简化了一张表里既能存结构化字段又能存向量字段过滤、排序、相似度计算可以在同一个 SQL 里完成。不用维护两套系统事务一致性和业务字段的实时性也天然得到保证。1.3 一体化的本质是就近计算我以前写过不少代码对数据不动代码动这句话感触特别深。一体化方案的底层逻辑就是把计算推到数据所在的地方。向量字段和业务字段在同一个存储引擎里查询时过滤条件直接下推到存储层执行而不是像两套库那样需要先把全部数据拉到应用层再算距离。数据量小的时候看不出差别数据量到了千万级这个差距就是几十毫秒和几百毫秒的差别。注意这里说的一体化不是说 PolarDB-X 要把向量数据库的活全干了。它解决的是“大多数业务场景下向量检索和结构化查询需要联合使用”的问题。如果你需要的是十亿级纯向量检索、极端低延迟独立向量数据库仍然有它的不可替代性。后面我会专门用一节讲边界。2. PolarDB-X 向量检索的底层原理与能力边界2.1 它是怎么在关系型存储里做向量匹配的先聊一个容易误解的点PolarDB-X 不是把向量塞进一个特殊字段就完事了而是在存储引擎层实现了向量索引结构再加上查询优化器的配合才能让ORDER BY 距离 LIMIT k这种 SQL 真正走索引加速。目前业内用得最多也最成熟的向量索引算法是 HNSWHierarchical Navigable Small World简单说就是给向量空间建一张多层图底层包含所有数据点越往上数据点越稀疏。检索的时候从顶层随机找一个入口点逐层往下走每层都只检查离目标最近的几个邻居。这样能把“全表暴力计算距离”从 O(n) 的复杂度降到大约 O(log n) 的级别。用生活里的例子类比HNSW 就像你在一座陌生城市找一家川菜馆。没有导航的时候只能挨家挨户看菜单暴力扫描有导航HNSW的时候你先看城市分区高层图锁定大概在哪个区再进街区逐条路找低层图效率自然完全不同。PolarDB-X 对向量索引的支持就建立在类似思想上同时保留了关系型数据库的看家本领MVCC 多版本并发控制、事务回滚、崩溃恢复。也就是说你往表里插入一条带向量的记录这个操作和普通 DML 一样具有 ACID 保证不会出现索引数据和表数据不一致的问题。2.2 支持哪些向量距离算法和 SQL 表达从我们实际使用的经验来看PolarDB-X 的向量能力覆盖了大多数常见场景。距离算法方面常用的余弦距离、欧氏距离、内积都有内置支持。实际场景里怎么选这里给个选型经验算法适用场景说明余弦距离文本/语义检索只关注方向不关心模长embedding 用 OpenAI、通义等模型生成时首选欧氏距离图像/特征匹配对向量模长敏感如果 embedding 做了 L2 归一化和余弦等价内积推荐/个性化排序适合需要同时考虑方向和模长的场景SQL 表达方面PolarDB-X 提供的是类标准 SQL 的扩展语法。简单说就是普通字段用 WHERE 过滤向量字段用专门的相似度函数计算距离最终通过 ORDER BY 排序 LIMIT 取 TopK。下面是我们建表和查询的真实写法-- 创建支持向量检索的表 CREATE TABLE product_embeddings ( id BIGINT PRIMARY KEY, product_id BIGINT NOT NULL, category_id INT NOT NULL, status TINYINT NOT NULL DEFAULT 1, title VARCHAR(512), embedding VECTOR(1024) ); -- 创建向量索引HNSW ALTER TABLE product_embeddings ADD VECTOR INDEX idx_embedding (embedding) ALGORITHM HNSW m 32 ef_construction 128;-- 查询找与目标向量最相似的 10 个已上架商品且限定在某个类目 SELECT product_id, title, VECTOR_COSINE_DISTANCE(embedding, :target_embedding) AS distance FROM product_embeddings WHERE status 1 AND category_id 1001 ORDER BY VECTOR_COSINE_DISTANCE(embedding, :target_embedding) LIMIT 10;这段 SQL 看起来平平无奇但执行计划已经变了先从 HNSW 索引的候选集里粗筛出一批向量近邻再用 WHERE 条件做精确过滤最后在过滤后的结果里精排输出。而不是先把所有满足 WHERE 条件的行读出来再一个个算距离。2.3 事务与向量检索的一致性这是很多团队忽略但实际非常关键的点。以前用两套库最怕的就是主库改了向量库没改。PolarDB-X 把向量字段当作普通字段一样管理和它同处一个事务内。具体行为事务 A 插入一条带向量的记录事务 B 在 A 提交之前是查不到这条记录的读已提交/可重复读隔离级别。事务 A 更新了某条记录的向量A 回滚后其他会话看到的仍然是旧向量。删除记录时对应的向量索引条目同步删除不会残留孤儿数据。这个能力在业务层面意味着什么意味着订单状态变为已发货和订单相关的语义向量更新可以放到同一个事务里提交要么都成功要么都失败。对于强一致性要求高的业务来说这是独立向量数据库做不到的。3. 一体化方案 vs 独立向量数据库 vs 旁路同步怎么选不后悔3.1 三种方案的完整对照选型这件事最怕的是拿着锤子看什么都是钉子。我的建议是先画一张全景表把每个方案的真实代价列清楚再回到自己的业务场景里去对应。维度PolarDB-X 一体化独立向量数据库关系库 旁路同步数据一致性强一致同事务弱一致靠双写最终一致靠同步任务查询复杂度SQL 直查业务过滤与向量检索一条语句需将向量结果回表关联业务数据需串行调用多系统再拼装事务支持完整 ACID基本不支持跨表事务业务库支持但向量库不支持运维成本一套系统两套系统三套系统含同步组件向量规模千万级以内最优亿级以上优势明显取决于中间件吞吐延迟表现毫秒级混合过滤场景优势突出纯向量检索极低延迟延迟最高生态兼容MySQL 协议现有代码基本零改造需学习新 API 新协议需维护同步链路单看这张表可能觉得一体化方案哪里都好。但选型不是看全面优势是看 要是这一步错了哪里最疼。独立向量数据库的不可替代性主要体现在向量规模到了亿级以上HNSW 图全量驻内存的内存开销不是关系型数据库愿意承受的。这部分 PolarDB-X 目前的索引结构还没有单独做磁盘化或量化压缩到极致。纯向量检索场景没有业务过滤条件QPS 要求上万甚至更高独立向量库针对这种情况做了大量内核级优化。如果你的向量检索团队和 OLTP 团队完全分开管理数据库选型往往不是纯技术问题而是组织架构问题。这种情况下强行一体化的推进成本反而更高。3.2 什么样的业务场景真正适合一体化方案我们在选型时定了一个判断框架分享出来供参考。满足其中任意两条以上就值得认真考虑一体化。第一业务数据的实时性对检索结果有直接影响。比如商品标题改了希望下一秒搜相似商品时用的就是新标题对应的语义向量而不是吃了上一天的同步任务。知识库、智能客服场景尤其如此文档改了问答机器人必须立刻反映变化。第二查询里同时混有强过滤条件和语义匹配。比如找相似商品但只在上架且库存0且类目为户外运动的范围内找。这种混合查询如果拆成两步向量库先召回 1000 条业务库再过滤掉 900 条会产生大量无效计算一体化方案里过滤下推是在召回之前做的效率完全不同。第三团队规模不大不想为了一个向量检索功能多养一套系统。这一点很现实。运维独立向量数据库得有专门的人看监控、调参数、扩节点、处理数据倾斜小团队根本扛不住。一体化方案的上手成本基本等于零只要会写 SQL 就能用。3.3 明确不适合的场景避免用错反过来说以下场景我建议还是老老实实用独立向量数据库。以非结构化数据为主、没有复杂业务关联的比如图片特征库、基因序列匹配一张表几乎只有一个向量字段和一个 ID。纯向量检索 QPS 要求极高且没有混合过滤需求的比如大规模内容去重、大规模人脸比对。向量数据量已经明确会快速超过亿级且增长看不到头。说白了没有任何一个方案是所有场景的最优解。一体化方案的定位是用 80% 常见场景的便利性换 20% 极端场景的极致性能这对大多数业务团队来说是一笔非常划算的买卖。4. 从零到一实战在 PolarDB-X 里跑通向量检索全流程4.1 环境准备与实例规格建议开始之前先确认你手上的 PolarDB-X 版本支持向量特性。根据我们的经验5.4.18 及以上的内核版本才有完整的向量检索能力旧版本没有。如果版本不够先升级内核再继续。实例规格方面我们压测后的结论是向量检索是典型的 CPU 密集 内存密集操作HNSW 索引会常驻内存。建议至少按单节点 8C32G起步向量维度越高、数据量越大内存越紧张。这里分享一个粗略估算公式内存估算 HNSW索引大小 原始向量数据大小 × 1.5缓冲余量以 1000 万条 1024 维 float32 向量为例原始向量大小 1000万 × 1024 × 4 字节 ≈ 38.15 GBHNSW 索引开销大约是原始数据的 1.2~1.5 倍取决于 m 参数也就是说至少准备 80~100GB 内存才稳妥别觉得夸张向量检索就是这么吃内存。如果预算有限一个折中方案是控制 HNSW 的 m 参数但换来的是召回率下降。这个后面章节详细讲。4.2 建表、导数、创建索引的完整步骤我们以商品语义检索为例完整跑一遍流程。第一步规划表结构。这里有一个关键点向量字段的类型定义要和 Embedding 模型输出的维度完全一致。如果用的通义千问 text-embedding-v3 输出 1024 维就定义 VECTOR(1024)用 OpenAI 的 text-embedding-3-small 是 1536 维就定义 VECTOR(1536)。不一致会直接报错别问我是怎么知道的。CREATE TABLE product_semantic ( id BIGINT NOT NULL AUTO_INCREMENT, product_id BIGINT NOT NULL, title VARCHAR(1024), description LONGTEXT, status TINYINT DEFAULT 1, created_at DATETIME, title_embedding VECTOR(1024), PRIMARY KEY (id) );第二步使用 PolarDB-X 的扩表语法可以按 product_id 做分区让数据均匀分布。分区键的选择直接影响查询性能建议选择查询里最常用的等值过滤条件作为分区键。ALTER TABLE product_semantic PARTITION BY HASH(product_id) PARTITIONS 32;第三步数据导入。向量数据建议分批写入单事务控制在 1000~5000 条左右。事务太大HNSW 索引的实时更新压力会很大事务太小导入效率又起不来。下面这段是 Python 端写入示例简化了连接池管理import pymysql import numpy as np from concurrent.futures import ThreadPoolExecutor def generate_embedding(text): # 调用 embedding 模型的伪代码实际用 qwen 或 openai sdk return [random.random() for _ in range(1024)] def batch_insert(items): conn pymysql.connect(hostyour-polar-db-x, useruser, passwordpass, databaseproduct_db) cursor conn.cursor() sql INSERT INTO product_semantic (product_id, title, title_embedding, status) VALUES (%s, %s, %s, %s) # 向量字段写入时需序列化为二进制或指定格式具体以驱动文档为准 cursor.executemany(sql, items) conn.commit() cursor.close() conn.close() # 用线程池并行写入但注意连接数不要打满实例 with ThreadPoolExecutor(max_workers8) as executor: for batch in batches: executor.submit(batch_insert, batch)第四步数据导入完成后创建 HNSW 向量索引。这里强烈建议先导数据再建索引而不是先建索引再导数据。原因是批量建索引比逐条插入时实时维护索引要快一个数量级而且索引质量更稳定。下面语法里 m 和 ef_construction 就是 HNSW 的关键超参ALTER TABLE product_semantic ADD VECTOR INDEX idx_title_embedding (title_embedding) ALGORITHM HNSW m 32 ef_construction 128;提示PolarDB-X 的向量索引创建是异步任务可以通过SHOW VECTOR INDEX STATUS查看进度。索引在 building 状态时查询仍可用只是不会走索引加速。4.3 核心查询混合过滤 向量排序建好索引后最重要的就是查询语法。这里演示三种最常见的查询模式。模式一纯向量 TopK只找最相似的。SELECT product_id, title, VECTOR_COSINE_DISTANCE(title_embedding, :query_embedding) AS distance FROM product_semantic ORDER BY VECTOR_COSINE_DISTANCE(title_embedding, :query_embedding) LIMIT 10;模式二向量检索 普通字段过滤最常用。业务上只看上架且属于某个类目的商品就是这种模式。SELECT product_id, title, category_id, VECTOR_COSINE_DISTANCE(title_embedding, :query_embedding) AS distance FROM product_semantic WHERE status 1 AND category_id 1001 ORDER BY VECTOR_COSINE_DISTANCE(title_embedding, :query_embedding) LIMIT 20;模式三用 HAVING 对相似度阈值做硬性过滤。在 RAG 场景里如果向量距离超过一定阈值说明 query 和文档语义相关性太弱不应该作为上下文喂给大模型。SELECT product_id, title, VECTOR_COSINE_DISTANCE(title_embedding, :query_embedding) AS distance FROM product_semantic WHERE status 1 ORDER BY VECTOR_COSINE_DISTANCE(title_embedding, :query_embedding) LIMIT 50 HAVING distance 0.35;这三条 SQL 基本覆盖了日常开发里九成以上的向量检索需求。4.4 与业务代码的完整链路整合SQL 这块通了业务代码就简单了。下面是 FastAPI 接口里做语义搜索商品的完整思路from fastapi import FastAPI, Query import pymysql import numpy as np app FastAPI() def get_embedding(text: str): # 真实环境里这里调用 embedding 模型接口 pass def search_similar(query: str, status: int, limit: int 10): query_embedding get_embedding(query) conn pymysql.connect(hostyour-polar-db-x, useruser, passwordpass, databaseproduct_db) cursor conn.cursor() sql SELECT product_id, title, VECTOR_COSINE_DISTANCE(title_embedding, %s) AS distance FROM product_semantic WHERE status %s ORDER BY VECTOR_COSINE_DISTANCE(title_embedding, %s) LIMIT %s cursor.execute(sql, (json.dumps(query_embedding), status, json.dumps(query_embedding), limit)) results cursor.fetchall() cursor.close() conn.close() return results app.get(/search) def search(q: str, status: int 1, limit: int 10): results search_similar(q, status, limit) return {results: results}整条链路跑通之后你会发现一个很爽的变化以前SQL 查业务 API 查向量 内存拼装结果的样板代码没了一个查询函数从头到尾出现问题也只需要看数据库日志就行。5. 参数调优与压测实录召回率、延迟和内存的一场博弈5.1 HNSW 核心参数的解释与选值建议HNSW 之所以效果好靠的是一组参数配合。如果你不理解这些参数背后的意义调优就是瞎试。下面是我对每个参数的理解和推荐值。m最大连接数图中每个节点最多连多少条边。m 越大图越密召回率越高但内存占用和构建时间也越大。推荐 16~32追求召回率可以到 48一般不建议超过 64。我们的压测数据m 从 16 调到 32召回率Recall10提升约 4.5%但索引构建时间上涨了 73%内存涨了 28%。ef_construction构建时的搜索宽度构建索引时从候选集里找邻居的宽度。值越大图质量越好召回率越高但构建越慢。128 是性价比最高的起点追求极致召回可以到 256再往上收益就很有限了。ef_search查询时的搜索宽度每次查询时动态指定的搜索范围不预先固定。ef_search 越大召回越高、延迟越高。我们一般让应用层把这个值做成可配置参数默认 64对召回率敏感的场景调到 128~256。直接给一张配置参考表方便抄作业场景mef_constructionef_search预期效果快速上线166432构建快内存低召回率略低标准配置3212864综合平衡大多数场景适用高召回要求48256128召回率高内存和延迟明显上升5.2 我们是怎么调出最优参数的当时压测是在 1000 万条商品标题 embedding 数据上做的向量维度 10248C32G 节点两个。整个过程分了四轮。第一轮先用 m32、ef_construction128 建索引测基线。结果 P95 延迟 38ms召回率 91.2%。看起来好像还行但我在压测报告里特意标了一行ef_search64的时候部分慢查询到了 91ms原因是过滤条件category_id1001命中的分区上数据分布不均匀有的分区 HNSW 图明显更密。第二轮把 ef_search 从 64 提到 128召回率提升到 95.6%但 P99 延迟飙到 72ms。这个延迟对在线接口来说有点不可接受。第三轮做了个组合优化把过滤字段(status, category_id)加了一个普通二级索引让 SQL 优化器在选择执行计划时能先通过这个索引快速锁定候选分区再走 HNSW。效果立竿见影P95 掉回 31ms。第四轮针对部分分区倾斜的问题把分区数从 16 调到 32。数据倾斜缓解后P99 从 72ms 降到 45ms召回率维持在 95.1%。这个过程中让我印象最深刻的是向量检索性能不只是向量索引参数的事业务过滤条件的索引设计和分区策略对最终效果的影响同样巨大。很多人只盯着 HNSW 参数猛调忽略了执行计划层面的优化有点可惜。5.3 内存与延迟的平衡点在哪压测过程中我记录了几个关键数据点整理出来供参考配置索引构建耗时内存占用P95 查询延迟召回率m16, ef6422 分钟41GB29ms86.7%m32, ef12838 分钟52GB38ms91.2%m48, ef25661 分钟67GB55ms94.8%m32, ef128 过滤索引38 分钟52GB31ms91.2%从数据可以明显看出盲目增大 m 和 ef_construction 到一定阶段后收益递减严重。性价比最高的组合反而是标准 m/ef 配合好过滤条件索引这一步做完延迟反而下来了内存也没额外多消耗。5.4 最容易踩的 5 个坑第一向量维度与模型输出不一致建表直接报错。建议在代码里写个断言取一条真实数据先验证维度再建表。第二创建向量索引时不看状态就上生产。异步建索引期间查询不走索引如果你忘了查状态上线之初的查询可能全是全表扫描延迟感人。我们统一在发布脚本里加了SHOW VECTOR INDEX STATUS的检查步骤索引状态为 Active 才继续执行后续流程。第三INSERT 批量写入时单事务过大。我们曾试过一次性写 5 万条HNSW 索引实时更新导致 CPU 直接打满应用侧查询全部超时。后来控制在每批 2000 条问题解决。第四ORDER BY 和 LIMIT 的顺序写反。SQL 里必须先 ORDER BY 距离再 LIMIT k写成LIMIT k ORDER BY ...会直接语法报错。第五忽略了 embedding 的归一化处理。用余弦距离时如果 embedding 没做归一化余弦距离和内积计算结果可能完全不一致。建议在写入前统一做一次 L2 归一化这样不管应用层用哪个距离函数结果语义都是一致的。6. 一体化方案的工程落地从 POC 到生产的几个必答题6.1 POC 阶段应该验证什么不只是压测很多团队做 POC 就测一个指标QPS 能到多少。但在真实业务里比 QPS 更重要的是和你的业务形态是否匹配。这里列一份 POC 检查清单混合过滤查询的召回率是否达到业务预期单独测 TopK 召回没有意义必须加上 WHERE 条件一起测。数据实时更新时旧数据被修改或删除查询结果是否正确反映最新状态用事务回滚场景测一遍。数据量增长到预期的 2~3 倍性能衰减曲线是否可接受建议构造最大预期数据量的 1.5 倍做测试。并发写入和查询混跑时HNSW 索引的更新是否会拖垮查询单独压查询和混合压写入查询结果差异很大。备份恢复流程是否包含向量索引的正确恢复有些系统本身支持了向量索引但备份恢复工具没跟上恢复出来的索引是坏的。这些问题在官方文档里不会主动告诉你但在生产环境一定都会遇到。6.2 权限、监控、告警怎么配套一体化的一个隐性收益是安全体系不用重建。PolarDB-X 的账号权限管理本身就是 MySQL 那套向量字段和普通字段在权限控制上完全一致某个账号只能读某个表那么他对表里的向量字段也只能读。这点比独立向量数据库要省心得多——那边要重新设计一套行级/列级权限策略通常还很简陋。监控方面除了常规的 CPU、内存、磁盘、慢 SQL 指标我建议额外关注两个向量索引的构建进度和构建失败事件。索引构建是异步的失败后不会自动重试得靠告警通知。查询里距离计算占比高的慢 SQL。有些 SQL 因为过滤条件选择性太差实际执行时还是会扫描大量向量这类语句要单独捞出来优化。6.3 业务代码改造量到底有多少这个可能是团队最关心的问题。以我们实际改造的经验一个典型的MySQL 存储业务 Flask/FastAPI 后端项目改造成本主要集中在这几个点建表 DDL 增加向量字段定义这是一个一次性成本。Insert/Update 语句多了向量字段的写入通常在 service 层统一处理业务代码改动不大。查询接口从先调向量库再查业务库改成一条 SQL这部分反而是减代码量。需要新增一个 embedding 生成模块不管选哪个方案这笔账都跑不掉。整体估算一个 5000 行代码规模的中型服务我们用了大约两个人日完成改造上线。对比之前维护两套数据库的长期成本这笔一次性的投入非常值得。7. 选型决策清单与真实场景回放7.1 一张表帮你做最终决策写了这么多最终选型还是得有章法。这里给一张决策清单表把决策因素和倾向选择列清楚拿去开会用很方便决策因素倾向 PolarDB-X 一体化倾向独立向量数据库向量数据规模≤ 3000 万1000维参考值亿级以上业务过滤条件必须混合使用几乎没有数据实时性要求秒级一致分钟级可接受团队运维能力少而精不想养两套有专职 DBA 和向量团队事务要求强一致不关心成本预算重视总体拥有成本有充足预算我个人的判断是2025 年了大部分业务团队的业务数据早就沉淀在关系型数据库里把向量检索能力长在关系型数据库上是架构收敛的大方向。独立向量数据库仍然有价值但它的价值更多聚焦在超大规模纯向量检索这个细分赛道不再是所有向量场景的默认选项。7.2 回放一次真实的架构决策会议最后分享一个我们开选型会时的实际场景。当时有两个方案在争论方案 A 是自建 Milvus 集群 现有 PolarDB-X 做双写方案 B 就是直接用 PolarDB-X 的向量能力。方案 A 的拥护者说Milvus 在百万级向量上 QPS 上千社区活跃没人会因为你用了主流向量库而质疑你。方案 B 的支持者包括我说我们要处理的核心场景是用户提问从 200 万条知识片段里找出相关内容同时过滤掉已经删除的、权限不可见的、分类不匹配的片段200 万量级PolarDB-X 完全扛得住而且权限过滤可以做成 SQL 条件直接下推Milvus 要么折腾 filter 表达式要么在应用层做二次过滤。最终我们用两天做了一个最小原型各写了 50 行代码对比效果。结论很明确原型阶段一体化的查询链路代码量大约是独立向量库方案的 40%数据不一致问题完全消失性能实测也能满足要求。方案 B 全票通过。所以我的建议是选型这种事别在会议室里争花两天时间把最小原型跑出来用数据说话答案自己就会浮出水面。7.3 最后补一个实操小技巧技术选型的最终落地还有一个很容易被忽略的细节embedding 模型的升级替换。不管选哪种方案模型升级后新旧向量语义空间不同混在同一张表里就会导致检索结果质量下降。我的做法是在业务表里加一个embedding_version字段查询时强制带上这个字段的过滤条件。这样模型升级时新旧版本数据可以共存灰度切换按比例放量一旦效果不好可以秒级回滚。这个技巧在我们团队的多个项目里都用过简单但极其有效。