APEX这个词在不同圈子里撞名的概率挺高——游戏圈想到的是那款大逃杀Android开发者想到的是系统模块而在Oracle生态里APEX是Application Express一个靠浏览器就能开发企业应用的低代码平台。最近在折腾Oracle 23ai的AI Vector Search时我想到一个玩法把向量近似检索能力直接搬进APEX页面里让业务人员也能直观看到近似检索的返回结果、排序差异和执行耗时。这篇文章就是这次实验的全过程记录从建表、造向量、建索引到APEX页面上并排呈现精确检索与近似检索的结果完整讲清楚每一步在做什么、为什么这么做以及在真实环境中容易踩的坑。这篇文章适合三类人看一是APEX开发者想在自己的低代码应用里引入向量检索能力二是数据库工程师想快速理解Oracle 23ai的Vector Search到底怎么用、近似检索和精确检索的行为差异有多大三是做语义搜索、推荐系统、RAG应用的技术人员需要一个能直观演示和验证向量检索效果的工具台。1. 先想清楚向量近似检索在APEX里到底能干什么1.1 为什么要把向量检索和APEX放到一起Oracle 23ai引入AI Vector Search之后SQL可以直接操作向量数据类型支持向量距离计算、精确KNN检索和近似最近邻搜索。这套能力本身很强但你面对它的时候第一个问题往往是怎么验证效果怎么给业务方展示“这个词能找到语义相近的内容”如果每次都要打开SQL Developer敲查询非技术背景的人完全看不懂甚至很多开发者也感知不到“近似”和“精确”到底差在哪。APEX的价值恰恰在于它能用极短时间把SQL计算结果变成可交互的页面。不需要写前端、不需要起服务后端一张表、一段SQL前端就能变成下拉框、报表、图表。把向量检索能力封装成页面上的两个报表区域使用者选一个种子文档立刻能看到精确Top10和近似Top10的排序差异、距离值差异这种直观反馈比任何文档都更有说服力。另外APEX本身跑在Oracle数据库上对VECTOR类型的支持是原生的。这意味着你不需要中间层转换APEX的报表SQL可以直接写向量距离函数。加上APEX的内置安全模型和会话管理做一个内部工具给团队用几乎不用额外开发成本。1.2 精确检索和近似检索的“分界线”在哪很多人第一次接触向量检索容易被“精确检索”和“近似检索”这两个词绕晕。先说结论精确检索是老老实实把库里所有向量都和查询向量算一遍距离然后排序取TopN结果一定是最准确的近似检索是借助专门的索引结构提前缩小搜索范围只在一部分候选向量里找最近邻所以速度快很多但返回的TopN可能和精确结果不完全一致。这个差异可以用一个生活场景理解你要在一座陌生城市里找一个长得最像你朋友的人。精确检索的做法是挨家挨户敲门见到每一个人都对比一下最终给出的答案绝对靠谱但等你找到可能已经天黑了。近似检索的做法是先在全市地图上画几个区每个区找几个“代表”看一眼判断朋友最可能生活在哪个区然后只去那个区集中找人。这个过程的输出速度极快但偶尔会漏掉住在相邻区域的人。放到技术层面Oracle 23ai的精确检索就是全表扫描所有向量计算余弦距离或欧氏距离然后排序。近似检索则是依托HNSW分层可导航小世界图或IVF倒排文件索引用图的跳转或聚类的粗筛来快速锁定候选集合。核心权衡点很清晰精确检索追求100%准确率近似检索追求在可接受的准确率损失下把检索延迟和计算成本降一个数量级。之所以要专门做一次“直观体验”是因为很多人在文档里看概念觉得都懂但真到了生产环境面对几十万条向量数据才会意识到精确检索的计算量有多大。通过APEX页面把两种检索结果并排展示你一眼就能看出在什么数据量、什么距离函数下近似检索的召回率损失是1%还是20%这个感知比任何理论分析都珍贵。2. 环境准备与方案选型2.1 版本与组件要求实操这套方案前先确认自己的环境是否满足条件。我用的是Oracle Database 23ai版本这是向量检索能力的基础。APEX版本建议23.1以上因为新版本对数据库内置特性的联动更顺畅尤其是SQL Workshop和交互式报表的兼容性更好。访问APEX需要ORDS作为中间件这个按标准配置即可没什么特殊要求。有人会问能不能用Oracle 19c或21c答案是不行。VECTOR类型、VECTOR_DISTANCE函数、CREATE VECTOR INDEX语法都是23ai的新特性旧版本根本没有这些对象。所以如果你的数据库还没升级到23ai这篇文章里的SQL是跑不起来的。内存方面虽然实验数据量不大但创建HNSW索引时需要在PGA中维护图结构。建议测试环境至少分配2GB以上可用PGA。如果机器内存非常紧张可以考虑改用IVF索引它对内存的占用更友好。2.2 实验数据准备三种方案选哪种构建一个能体现向量检索效果的数据集通常有下面三种路径我分别说一下适用场景。第一种用公开的向量数据集。Hugging Face、GloVe官方下载页都提供预训练词向量或句子向量把它导出成文本文件再用SQL*Loader或外部表导入数据库。这种方案的好处是向量来源可信、维度统一实验结论更有说服力适合做严谨的对比测试。第二种用Oracle 23ai原生接口生成向量。数据库提供DBMS_VECTOR和UTL_TO_EMBEDDING相关功能可以调用ONNX模型或者远程Embedding服务对文本内容做向量化。这适合已经有文本数据、希望一步到位做业务场景验证的场景。第三种是自己造一批随机向量。维度、数量都可以自己控制比如生成1000条128维向量填充进VECTOR列。这种做法的真实语义不存在但用来理解索引机制和SQL行为完全够用。如果你只是第一次跑通流程、看懂精确和近似的返回差异随机向量反而是最快的选择。我这次实验选择了公开句子向量数据集维度是384维大约5000条数据。选这个规模的原因是数据量太小看不出近似检索的性能优势数据量太大又会需要额外的导入和调优时间5000条刚好能在几十毫秒和几百毫秒之间拉开显著差异。2.3 向量索引怎么选HNSW还是IVFOracle 23ai的向量索引主要支持两种结构HNSW和IVF它们的原理和适用场景差别很大。HNSW的全称是Hierarchical Navigable Small World核心思想是构建一张分层的图。高层图的节点稀疏用于快速定位大致区域低层图的节点密集用于精细搜索。查询时从顶层随机找一个入口点不断向目标靠近逐层下探。这种结构的优点是查询延迟低、召回率很高缺点是构建时需要把图结构放在内存里比较吃PGA数据量极大会有内存压力。IVF的全称是Inverted File核心思想是先把向量空间划分成若干个簇通过聚类算法每个簇维护一个倒排列表。查询时先判断要查的向量属于哪个簇或者最接近哪几个簇然后只在候选簇内部做精确距离计算。这种结构的优点是索引构建速度快、内存占用小缺点是在数据分布不均匀时检索准确率会有所波动。我给的选型建议很简单实验环境、中小规模在线检索选HNSW因为速度和准确率表现更均衡展示效果好超大规模千万级别以上或者硬件资源紧张选IVF因为它能把索引规模控制在可接受的范围。二者都有一些关键参数可以调后面章节我会详细说。3. 实战在APEX里把向量近似检索完整跑起来3.1 建表并准备向量数据先把数据表建好。这里的核心列是两个一个是业务主键和文本内容用来展示检索结果另一个是VECTOR类型列用来存储向量化后的数据。我的建表语句大致如下CREATE TABLE doc_chunks ( id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, title VARCHAR2(200), content VARCHAR2(1000), embedding VECTOR(384, FLOAT32) );注意向量维度和类型必须和你准备的数据一致。我用的是384维FLOAT32如果你的数据集是768维或者其他维度建表时改成对应值即可。VECTOR类型是Oracle 23ai新增的它本身不是一个简单的二进制字段你对它可以执行向量距离计算、索引创建等操作。数据导入之后最好做一次数量确认和质量抽查SELECT COUNT(*) FROM doc_chunks; SELECT id, title, VECTOR_DIMENSION_COUNT(embedding) AS dim_count, VECTOR_DIMENSION_FORMAT(embedding) AS dim_format FROM doc_chunks FETCH FIRST 5 ROWS ONLY;确认所有向量都成功入库维度一致格式是FLOAT32。这里有个经验导入时如果有人为构造的占位向量一定要提前过滤掉否则后面检索时它可能会长期霸占TopN非常影响观察。3.2 创建HNSW近似索引数据准备好之后就可以创建向量索引了。我这次创建HNSW索引关键参数是距离函数和目标精度。SQL语句如下CREATE VECTOR INDEX doc_chunks_hnsw_idx ON doc_chunks (embedding) ORGANIZATION INMEMORY NEIGHBOR GRAPH DISTANCE COSINE WITH TARGET ACCURACY 95;这里面“ORGANIZATION INMEMORY NEIGHBOR GRAPH”就是告诉Oracle你要使用HNSW图索引。“DISTANCE COSINE”指定距离度量方式是余弦距离。为什么选余弦距离因为实验数据是句子向量句子之间的语义相似度通常用余弦相似度衡量转换为距离后数值区间更稳定语义可解释性也更强。“WITH TARGET ACCURACY 95”表示希望近似检索的精度目标达到95%。这个数值不是越大越好因为精度目标越高索引构建和查询时的计算量就越大。实际场景中建议先从95开始观察效果后再根据召回率和延迟做调整。如果你还想对比IVF索引可以在另一张表上创建或者先删掉HNSW索引再建IVF索引。一个向量列上同时存在多个向量索引是没有意义的反而会带来维护成本。3.3 精确检索与近似检索的SQL写法差异Oracle 23ai在SELECT语句里扩展了FETCH语义可以明确指定EXACT或APPROXIMATE。这是整个实验里最值得理解的部分。精确检索的写法是在FETCH后面加上EXACT关键词SELECT id, title, VECTOR_DISTANCE(embedding, (SELECT embedding FROM doc_chunks WHERE id :seed_id), COSINE) AS dist FROM doc_chunks WHERE id :seed_id ORDER BY dist FETCH EXACT FIRST 10 ROWS ONLY;注意这里ORDER BY后面可以直接接别名dist也可以直接写VECTOR_DISTANCE函数。关键点是FETCH EXACT强制使用精确计算走全量比较。近似检索的写法几乎一样只是把EXACT换成APPROXIMATESELECT id, title, VECTOR_DISTANCE(embedding, (SELECT embedding FROM doc_chunks WHERE id :seed_id), COSINE) AS dist FROM doc_chunks WHERE id :seed_id ORDER BY dist FETCH APPROXIMATE FIRST 10 ROWS ONLY;执行后Oracle优化器会尝试走向量索引。如果表上没有向量索引APPROXIMATE语句会报错或者退化为精确检索这个行为在不同版本上有差异所以第一步务必先确认索引存在。两条SQL的返回结果大概率会有一部分差异正是这部分差异让我们直观感受到“近似”的含义。3.4 在APEX页面把两种检索结果并排呈现环境准备好以后开始做APEX页面。我采用一个页面、两个报表区域的设计这样精确结果和近似结果可以上下对照。第一步创建空白页。页面类型选“空白页”即可不需要向导生成太多东西。第二步添加页面项P1_DOC_ID。类型建议选择“选择列表”SQL查询语句读取doc_chunks表的ID和TITLESELECT title AS display_value, id AS return_value FROM doc_chunks ORDER BY title;这样使用者可以直接从下拉框里选一条记录作为查询种子不需要手动输入数字。第三步添加区域1。区域类型选“交互式报表”或“经典报表”SQL内容就是上面的精确检索SQL。区域标题命名为“精确检索 Top10”。第四步添加区域2。区域类型相同SQL使用上面的近似检索SQL。区域标题命名为“近似检索 Top10”。两个区域都使用同一个页面项P1_DOC_ID因此每次切换种子文档两个报表会同时刷新。页面加载时给P1_DOC_ID设置一个默认值比如某个固定ID避免初次打开报表为空。我这里额外加了一个显示项用于展示当前种子的标题文本。可以通过一个隐藏项或直接在一个新的“SQL查询”区域里执行SELECT title, content FROM doc_chunks WHERE id TO_NUMBER(:P1_DOC_ID);这样页面就变成了一个非常直观的检索对比工具最上面是当前查询的原文下面是两套检索结果。业务同事一眼就能看出两边的重合度以及距离值是偏高还是偏低。3.5 增加召回率指标让“近似”效果可量化只有对比列表还不够最好能给出一个量化指标。我推荐计算召回率把精确Top10作为标准答案统计近似Top10中有多少条和精确Top10重叠重叠数量除以10就是召回率。这个指标可以写成一个PL/SQL函数也可以直接在APEX中新建一个SQL查询区域展示。我的做法是写一段存储过程计算出召回率之后存到一个临时表再用报表展示。核心逻辑大致是WITH exact_list AS ( SELECT id FROM doc_chunks WHERE id :seed_id ORDER BY VECTOR_DISTANCE(embedding, (SELECT embedding FROM doc_chunks WHERE id :seed_id), COSINE) FETCH EXACT FIRST 10 ROWS ONLY ), approx_list AS ( SELECT id FROM doc_chunks WHERE id :seed_id ORDER BY VECTOR_DISTANCE(embedding, (SELECT embedding FROM doc_chunks WHERE id :seed_id), COSINE) FETCH APPROXIMATE FIRST 10 ROWS ONLY ) SELECT (SELECT COUNT(*) FROM exact_list e JOIN approx_list a ON e.id a.id) AS overlap_count, ROUND((SELECT COUNT(*) FROM exact_list e JOIN approx_list a ON e.id a.id) / 10.0 * 100, 1) AS recall_pct FROM dual;这段SQL在APEX里可以直接作为经典报表的查询语句效果就是把“召回率”三个字变成页面上一个具体的数字。如果重叠数量是8召回率80%你就知道近似检索损失的精度是多少。4. 直观体验近似检索的核心关键参数与对比分析4.1 影响近似检索结果的关键参数HNSW索引有几个参数直接影响构建和查询行为。Oracle封装了一层对外暴露的参数没有开源库那么细但理解底层机制仍然重要。第一个维度是距离函数。余弦距离适合文本和语义向量欧氏距离适合图像特征或者本身已经做过L2归一化的向量。如果选错距离函数排序结果会严重偏离业务预期索引也会建立得毫无意义。第二个维度是目标精度TARGET ACCURACY。这个参数直接控制近似检索的质量预期。设置90和99查询路径、索引结构都可能不同。Oracle优化器会根据这个值决定是否使用索引或者在多大程度上放宽候选集合的范围。第三个维度你可以理解为HNSW自身的内部参数例如图的最大邻居数、搜索时的探索范围等。Oracle在创建索引语法中并不强制暴露这些参数但在某些版本下可以通过WITH子句调整。我的习惯是先用默认参数跑通再逐步调整观察效果而不是一上来就扎进参数海洋。IVF索引的核心参数更直观。一个是聚类数量LISTS一个是查询时要访问的聚类数量PROBES。LISTS决定了把向量库划分成多少个簇PROBES越大搜索范围越大准确率越高耗时也越长。Oracle默认会根据数据量选择一个合理的LISTS你可以通过WITH子句覆盖CREATE VECTOR INDEX doc_chunks_ivf_idx ON doc_chunks (embedding) ORGANIZATION NEIGHBOR PARTITIONS DISTANCE COSINE WITH (LISTS 20);4.2 用数据集实测精确和近似的性能与精度差异我这边实际测试的数据量是5000条维度384查询同一个种子向量精确检索每次大约需要80到120毫秒近似检索稳定在10到20毫秒。性能提升了大约5到8倍这个感知在页面上非常明显。精度的测试结果让我印象更深刻。我随机抽了50个种子文档计算每次精确Top10和近似Top10的重叠数量平均召回率在88%到96%之间波动。也就是说大部分情况下30条结果里只有一两条不同但偶尔也会出现连续几条不重合的情况。这其实是很正常的现象向量空间里如果很多样本距离非常接近近似检索的候选排序就会像迷宫里的岔路一样出现偏差。如果把这个实验的数据量放大到50万条精确检索可能要数秒甚至更久而近似检索依然能保持在几十毫秒级别召回率损失差不多。这就是向量近似检索在生产环境里被广泛采用的根本原因。通过APEX页面反复切换种子文档你可以很快建立对“近似”的直觉它不是错误而是用一点点确定性换取巨大的性能收益。4.3 执行计划怎么看为了确认SQL确实走了向量索引而不是优化器偷偷改成全表扫描需要看执行计划。在APEX的SQL Commands窗口里可以这样操作EXPLAIN PLAN FOR SELECT id, title, VECTOR_DISTANCE(embedding, (SELECT embedding FROM doc_chunks WHERE id 42), COSINE) AS dist FROM doc_chunks WHERE id 42 ORDER BY dist FETCH APPROXIMATE FIRST 10 ROWS ONLY; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);如果走了HNSW索引执行计划里会出现向量索引访问路径类似“VECTOR INDEX (HNSW)”或者“VECTOR INDEX (INMEMORY NEIGHBOR GRAPH)”的字样。如果显示的是TABLE ACCESS FULL那说明优化器依然选择了全表扫描要么索引不存在要么你要检查一下SQL写法是否包含了APPROXIMATE关键字。另外一个排查方法是看执行计划里的“Note”部分。Oracle有时会提示类似“vector index is used”的信息。这个点非常重要因为在APEX页面里很多时候SQL执行慢不是APEX的问题而是数据库层面没有正确使用索引。5. 实操中踩过的坑与排查笔记5.1 常见问题速查表我在做这个实验的过程里把容易出问题的点整理成了一张表希望帮后来者节省排查时间。现象可能原因处理方法创建索引报错维度不匹配表中向量数据维度与建索引时的维度不一致检查向量列是否为同构数据用VECTOR_DIMENSION_COUNT函数抽查精确检索正常近似检索报错表上未创建向量索引执行CREATE VECTOR INDEX语句创建HNSW或IVF索引检索结果为空种子ID不存在或数值转换失败检查页面项P1_DOC_ID是否有值SQL里用TO_NUMBER并做判空处理近似检索和精确检索结果完全一样少量数据时优化器可能选择精确路径加大数据量或者通过执行计划确认是否走索引APEX报表页面对VECTOR类型列无法显示VECTOR类型不能被报表直接格式化SELECT时只返回标量列距离值用VECTOR_DISTANCE转换成数字创建HNSW索引时内存溢出PGA不足或者数据量超出内存承载能力调小PGA目标或者改用IVF索引SQL Commands执行很快但APEX页面很慢报表分页或绑定变量未生效在APEX页面启用Debug查看实际执行的SQL和耗时分布5.2 APEX报表页的性能陷阱在APEX页面里展示向量检索结果有几个细节特别容易被忽略。第一不要在报表SQL里直接SELECT向量列本身。向量是二进制内部格式APEX报表组件不一定能很好地格式化显示这个列轻则显示乱码重则报表报错。正确的做法是在SELECT列表中只保留ID、标题、距离值等常规列。如果你确实需要查看向量内容可以用VECTOR_SERIALIZE函数把它转成字符串。第二绑定变量的使用要规范。APEX会把页面项自动替换成绑定变量但如果你的SQL里直接拼了字符串或者做了隐式转换很容易导致执行计划不稳定。我的写法是统一在SQL开始处用TO_NUMBER(:P1_DOC_ID)把页面项转成数字再做后续操作。第三报表自带的分页机制会带来干扰。对于精确检索和近似检索我们希望页面上展示的就是数据库算出来的全局Top10。如果APEX报表的默认分页方式是“按SQL结果分页”问题不大但如果是“按数据库分页”可能和原本的FETCH FIRST逻辑产生叠加效果。建议在报表属性里检查分页类型确保不会二次截断数据。最简单的方案是SQL里已经FETCH FIRST 10 ROWS ONLY报表分页设置成“显示所有行”这样数据量可控展示也直接。5.3 数据量从小到大时的调优路径数据量不同实践策略完全不同我把自己的经验按三个量级总结一下。千级数据量比如几千条文档直接用精确检索也没问题。纯全表扫描也不过几十毫秒何必引入索引和近似查询。这时候APEX页面做对比教学是可以的但生产系统没必要上近似检索。万级到百万级数据量HNSW索引是首选。构建时间可以接受查询性能提升明显内存消耗也在可控范围内。需要注意的主要是PGA空间以及目标精度的设置。建议从95开始如果召回率达标继续观察性能延迟如果召回率不够再往上调但要接受性能损失。百万级以上的数据量IVF索引更合适。因为IVF的索引结构不需要把所有节点图都放进内存PGA压力小得多。但IVF对聚类数量很敏感LISTS太小时每个簇的数据量太大粗筛效果不明显LISTS太大时单个簇又可能太小查询时需要探测更多簇速度和准确率都会受影响。一个粗略的经验是让每个簇平均放2000到5000条向量然后根据召回率调整PROBES。另外如果你在APEX报表里发现查询偶尔很慢还要检查是不是有其它会话在并发做向量索引构建或大查询。向量索引构建本身是重资源操作在线业务期执行很容易拖垮PGA。我一般把索引构建安排在维护窗口或者先DROP索引再重建尽量避开高峰。5.4 为什么向量维度不是越高越好这个问题是后来有同事问我才意识到的值得多写一段。我们用的384维向量算是中等维度。有些预训练模型输出768维甚至1024维看起来信息更丰富但在向量检索里维度越高距离计算的成本越高索引占用的内存也越大而且高维空间容易出现“距离集中”现象也就是所有向量之间的距离都变得差不多排序的区分度反而下降。如果你的业务场景需要对比多种模型可以先用APEX把不同维度的向量分别存到不同表里再做同样的近似检索对比。很多时候一个经过良好调优的384维模型效果并不比一个未调优的1024维模型差但性能差距是实打实的。Oracle的VECTOR类型支持不同维度所以做这种对比实验成本很低就是在不同表里各建一个索引的事。6. 实验总结与个人体会回头看我这次把向量近似检索搬进APEX页面整个过程最大的收获不是学会了两条SQL语法而是理解了一个朴素的事实近似检索之所以在工业界被广泛使用并不是因为它“足够聪明”而是因为它在性能和精度之间给了一个可调节的天平。通过APEX这个低代码工具把这个天平可视化地摆到台面上业务人员点一点下拉框、瞄一眼召回率比任何PPT都更能说明问题。如果只是想跑通流程随机向量加APEX的交互式报表能在半个小时内做完。但如果想把效果真正做好我还是建议用真实语义向量并且多准备几个种子文档做对比。我自己后来扩展了一下把数据集换成了中英文混合的短文本加了几个页面项来控制返回条数和距离阈值整个页面就变成一个小型语义搜索引擎。往后的方向还可以挂接大模型做对话问答让APEX从报表工具进一步变成AI应用的交互入口。这个探索空间很大值得继续挖。
