OceanBase 花三年时间整理出的向量数据库最佳实践
楔子
AI 时代,各种 AI Infra 都离不开向量数据的存储、检索。而做向量场景的 PoC,每次都要重新算一遍内存、选一遍索引、调一遍参数……
OceanBase 有两位不愿意透露姓名的大佬——序风、舸灏,在最近三年支持了无数 AI 场景的向量数据库 PoC(Proof of Concept)工作,有着究极丰富的向量数据库运维和调优经验。
这篇文章,是他们第一次把压箱底的向量数据库运维经验总结拿出来,在社区公众号为大家进行分享。文中包含了使用向量数据库时,需要考虑的方方面面。
(本文适合收藏,以备不时之需)

欢迎大家关注 OceanBase 社区公众号”老纪的技术唠嗑局”。

本文涵盖了:向量索引选型、内存与 CPU 规划、磁盘空间估算、分区设计、索引参数配置、混合查询调优、性能验证方法与实测数据、常见性能问题排查等等。适用于向量数据库 PoC 评估、向量索引类型选择、租户资源规划和查询性能调优。
本文的前置阅读材料是 《浅入了解向量数据库》。
上篇:向量构建设计实践
【选型与规划】 这一部分会讲清楚:什么场景选什么索引、内存 CPU 磁盘怎么算、表怎么建、参数怎么配。
1. 索引选型
开篇就给结论:选型不靠经验,靠两条数据——数据规模和内存预算。
快速决策树如下:

上图的决策逻辑简述:数据规模 < 1000 万不分区,1000 万 ~ 5 亿和 > 5 亿均做分区(单分区 1000 万)。< 5 亿且内存充裕选 HNSW 或 HNSW_SQ,内存有限选 HNSW_BQ;> 5 亿统一选 HNSW_BQ。内存极低时按维度选 IVF——维度 < 384 选 IVF_FLAT,≥ 384 选 IVF_PQ。
注意:HNSW_BQ 要求维度 ≥ 384, IVF_PQ 要求维度 ≥ 128。单分区 1000 万向量并不是强制要求,通常建议大数据量下单分区控制在 500 万~2000 万向量,单个分区的向量数不能超过 5000 万。
| 案例场景 | 推荐方案 | 详细章节 |
|---|---|---|
| 384 维,亿级,geohash 过滤 | HNSW_SQ 或 HNSW_BQ,按 geohash 分区 | 第 4 章、第 6 章 |
| 1024 维,十亿级,多维过滤 | HNSW_BQ,skill_id 一级分区,doc_id 二级分区 | 第 4 章 |
| 768 维,千万~亿级 | HNSW_SQ 或 HNSW_BQ | 第 1 章 |
| 任意维度,十亿级,只增不删 | IVF_PQ | 第 1 章、第 2 章 |
1.1. HNSW 系列
HNSW 系列索引是内存索引,查询时需要长驻内存,注意它不是缓存,无法像 KV Cache 那样临时换出。在重建时会有一段时间内存中存在新旧两份索引。
| 类型 | 内存(相对 HNSW) | 召回率 | 查询性能 | 适用场景 |
|---|---|---|---|---|
| HNSW_SQ | 1/4 ~ 1/3 | 略低于 HNSW | 最高 | 千万级首选,性能和内存的平衡点 |
| HNSW_BQ | 1/20 | 低于 SQ,需 refine 补偿 | 中高 | 亿级以上,内存有限时的唯一选择 |
如果内存成本足够,HNSW_SQ 是大多数场景下的最佳选择。HNSW_BQ 的极致量化(RapidQ)让索引本身极小,但查询时需要从磁盘捞原始向量做重排,因此磁盘性能对其性能有一定影响,TopK 越大影响越明显。

1.2. IVF 系列
IVF 索引常驻磁盘,内存占用极低,适合内存预算极度有限的场景。
| 类型 | 查询性能 | 构建速度 | 召回率 | 适用场景 |
|---|---|---|---|---|
| IVF_FLAT | 较慢 | 快 | 高 | 内存紧张但维度不高(<384) |
| IVF_PQ | 比 FLAT 快 | 慢 | 略低 | 维度高(≥384)、内存极度紧张 |
如果大家要拿多种不同的向量数据库对比性能,则一定要注意区分索引类型:磁盘索引的 RT 通常比内存索引高数倍,这点各家是都一样的。不能单纯按照索引名称对比性能,而要按照实际的内存 / 磁盘索引类型来对比。

注意:IVF 系列索引在 OceanBase 4.6.0 版本以前都不支持堆表的分区表。IVF_PQ 使用 l2 距离时需额外缓存预计算结果,大规模场景优先用 cosine 距离。IVF 系列索引,随表创建索引后写入数据相当于暴力搜索,要在写完数据后重建 IVF 索引。
2. 资源规划
PoC 报方案前最怕被反问”内存够不够、机器买几台”——这一章给你一个最直观的算法。

2.1. 内存估算与规划
OceanBase 自带了向量索引内存估算函数 dbms_vector.index_vector_memory_advisor,可根据索引类型、数据规模和参数计算所需向量内存:
1 | -- 1 亿条 384 维向量,单分区最多 1000 万条 |
参数依次是:索引类型、总数据量、维度、数据类型、索引参数、单分区最大行数。
1024 维(最常见,text-embedding-3-large、bge-large 等):
| 数据规模 | 推荐索引 | 分区 | 向量内存(单副本) |
|---|---|---|---|
| 100 万 | HNSW_SQ | 不分区 | 2.7 GB |
| 500 万 | HNSW_SQ | 不分区 | 14.2 GB |
| 1000 万 | HNSW_SQ | 不分区 | 28.4 GB |
| 3000 万 | HNSW_BQ | 3 分区 | 构建 34.4 GB,运行时 17.2 GB |
| 1 亿 | HNSW_BQ | 10 分区 | 构建 74.6 GB,运行时 57.4 GB |
| 10 亿 | HNSW_BQ | 100 分区 | 构建 591.2 GB,运行时 574.0 GB |
| 10 亿 | IVF_PQ | 100 分区 | 构建 6.3 GB,运行时 1.9 GB |
768 维(text-embedding-3-small、bge-base 等):
| 数据规模 | 推荐索引 | 分区 | 向量内存(单副本) |
|---|---|---|---|
| 1000 万 | HNSW_SQ | 不分区 | 22.6 GB |
| 1 亿 | HNSW_BQ | 10 分区 | 构建 67.8 GB,运行时 53.8 GB |
| 10 亿 | HNSW_BQ | 100 分区 | 构建 552.2 GB,运行时 538.2 GB |
| 10 亿 | IVF_PQ | 100 分区 | 构建 4.8 GB,运行时 1.4 GB |
同样是 10 亿向量、单分区 1000 万:HNSW_BQ 构建峰值 591.2 GB,IVF_PQ 运行时 1.9 GB——内存差距约 300 倍。 这就是为什么内存预算决定了索引选型。

注意:向量内存估算函数计算的是单副本下的内存用量。租户内存 = 向量内存 ÷ ob_vector_memory_limit_percentage(默认 50%)。
HNSW 内存详解
| 索引类型 | 构建时内存 | 运行时常驻 | 说明 |
|---|---|---|---|
| HNSW | 76.3 GB | 76.3 GB | 全量常驻,不释放 |
| HNSW_SQ | 22.6 GB | 22.6 GB | 量化后常驻,约 HNSW 的 1/3 |
| HNSW_BQ | 22.6 GB | 5.4 GB | 构建时需要 SQ 缓存,完成后只剩 BQ 索引 |
IVF 内存详解
| 索引类型 | 索引参数 | 构建时内存 | 运行时常驻 |
|---|---|---|---|
| IVF_FLAT | nlist=3000 | 3.4 GB | 13.2 MB |
| IVF_PQ (cosine) | nlist=3000, m=384 | 3.4 GB | 14.3 MB |
| IVF_PQ (l2) | nlist=3000, m=384 | 5.0 GB | 1.7 GB |
l2 距离下 IVF_PQ 的常驻内存是 cosine 的 120 倍——因为 l2 要额外缓存预计算结果。
2.2. CPU 与 NUMA 对向量查询的影响
相比普通 SQL 查询,向量搜索的瓶颈主要在内存带宽,以及 CPU 支持的 SIMD 指令集。核数不是越多越好:特别是 64 核以后,每核分到的内存带宽减少、L3 cache 抢得厉害。核数不是越多越好。向量搜索的瓶颈是内存带宽和 SIMD 指令,超过 64 核之后,跨 NUMA 访问和 L3 cache 争抢会让性能不升反降。
2.3. 磁盘空间与查询性能
| 索引类型 | 磁盘估算 |
|---|---|
| HNSW | 约等于原始向量大小 × 1.2 |
| HNSW_SQ | 约等于原始向量大小 × 1.2 / 3 |
| HNSW_BQ | 约等于原始向量大小 × 1.2 / 20 |
| IVF_FLAT | 约等于原始向量大小 |
| IVF_PQ | 约等于原始向量大小 / 8 |
原始向量大小计算公式:行数 × 维度 × 4 字节,例如 1 亿 384 维 float32 = 144 GB。
总的说,几种索引算法受磁盘性能影响的程度:HNSW_SQ < HNSW_BQ < IVF/IVF_PQ。
3. 重要配置项说明
3 个参数,调与不调差距可能是 QPS 翻倍。
1 | -- 1. 并行构建采样精度(千万级 5000,亿级 10000,十亿级以上 100000) |
4. 表结构与分区设计
同时满足以下两条,建议分区:数据量千万级以上、查询条件里有明确的标量列能用来裁剪分区。目标单分区:500 万 ~ 2000 万行。结论:在满足 500-2000 万的前提下,分区越少越好。
| 总数据量 | 分区数 | 单分区量 |
|---|---|---|
| 5000 万 | 5-10 | 500-1000 万 |
| 1 亿 | 10-20 | 500-1000 万 |
| 4.5 亿 | 25-45 | 1000-1800 万 |
| 10 亿 | 50-100 | 1000-2000 万 |
二级分区(两个维度过滤,如 skill_id + doc_id):
1 | CREATE TABLE iop_knowledge ( |
“稀疏”的向量列
实测:主表 9 亿行,768 维列仅 2600 万行有值。大表上查 RT 21ms,拆到小表后降到 3ms 以内。9 亿行的主表查 21ms,拆出非空 2600 万行到独立小表后降到 3ms——延迟下降 7 倍。向量列稀疏时,大表分区扫描的隐藏开销远比想象高。
5. 索引创建与参数配置
强烈建议全量数据导入后再创建索引。并行度设租户 CPU 的 2 倍。
1 | -- HNSW_BQ |
HNSW 系列参数
| 参数 | 默认值 | 范围 | 作用 |
|---|---|---|---|
| distance | 必填 | l2 / cosine / inner_product | 多数 embedding 用 cosine |
| type | 必填 | hnsw / hnsw_sq / hnsw_bq | — |
| m | 16 | [5, 64] | 每节点最大邻居数 |
| ef_construction | 200 | [5, 1000] | 构建时候选集大小 |
| ef_search | 64 | [1, 16000] | 查询时候选集大小 |
| refine_k | 4.0 | [1.0, 1000.0] | 仅 BQ,重排比例 |
| refine_type | sq8 | sq8 / fp32 | 仅 BQ |
按规模的参数推荐
百万级:HNSW_SQ(m=16,ef_construction=200,ef_search=240);HNSW_BQ(m=16,ef_construction=200,ef_search=240,refine_k=4)
千万级:HNSW_SQ(m=32,ef_construction=400,ef_search=350);HNSW_BQ(m=32,ef_construction=400,ef_search=1000,refine_k=10);IVF_PQ(nlist=3000,m=dim/2,nbits=8,nprobes=20)
亿级(分区表):参数按单分区最大数据量来定。
增量与重建
索引创建后增量写入的向量立即可查,但增量部分不做量化压缩,会额外占内存。HNSW 系列增量达 20% 时自动触发后台重建,IVF 新增超 30% 后需手动 CALL dbms_vector.rebuild_index()。
6. 查询与调优
不带标量过滤:
1 | SELECT id, cosine_distance(embedding, @query_vector) AS distance |
APPROXIMATE 必须写(简写 APPROX 也行)。
混合查询(geohash + 向量):
1 | SELECT id, picturename, cosine_distance(picturevector, @query_vector) AS distance |

召回与延迟的权衡
ef_search(HNSW 系列)和 nprobes(IVF)是核心旋钮。
HNSW 系列(768 维,千万级,目标 Recall ≈ 0.95):Top10 ef_search=100; Top100(HNSW_SQ) ef_search=350; Top100(HNSW_BQ) ef_search=1000, refine_k=10
IVF 单分区(千万级):Top10 nprobes=1; Top100 nprobes=20; Top1000 nprobes=90
7. 性能验证
四个核心指标:QPS、平均 RT、P95/P99 RT、召回率。

召回率测试:准备 100 个以上查询向量,分别跑精确搜索和近似搜索,对比结果:
1 | -- 精确搜索(ground truth) |
压测时最好进行合并(major freeze)和预热,减小回表和读盘对查询性能的影响。
实测性能参考:
百万级 768 维(m=16, ef_construction=200, Top100, ef_search=240):
| 索引类型 | QPS | 召回率 | 内存 |
|---|---|---|---|
| HNSW | 3475 | 0.9499 | 7.3 GB |
| HNSW_SQ | 5599 | 0.9468 | 2.1 GB |
| HNSW_BQ (refine_k=4) | 3113 | 0.9278 | 0.4 GB |
千万级 768 维(m=32, ef_construction=400, Top100):
| 索引类型 | QPS | 召回率 | ef_search |
|---|---|---|---|
| HNSW | 2637 | 0.9574 | 350 |
| HNSW_BQ (refine_k=10) | 857 | 0.9531 | 1000 |
4.5 亿 384 维混合查询(HNSW_SQ, m=32, 45 分区,20 并发):
| geohash 过滤个数 | QPS | 平均 RT |
|---|---|---|
| 10 | 810 | 24ms |
| 20 | 717 | 28ms |
| 40 | 488 | 40ms |
8. 向量查询性能问题排查手册
出问题时不要乱调参数,按这张表的”现象 → 原因 → 动作”走,90% 的问题能在 10 分钟内定位。

| 现象 | 最可能的原因 | 动作 |
|---|---|---|
| 延迟秒级 | 没做分区裁剪 | EXPLAIN 看 partitions 字段 |
| 延迟秒级 | 没加 APPROXIMATE | EXPLAIN 确认是否走了向量索引 |
| P99 >> P50 | 部分分区索引没加载到内存 | 查 GV$OB_VECTOR_MEMORY |
| RT 偏高 | 向量列大量 NULL,大表扫描开销 | 拆非空行到小表,实测 RT 从 21ms 降至 3ms |
| 召回低 | ef_search / nprobes 太小 | 逐步调大 ef_search 或 nprobes |
| 混合查询慢 | 标量字段没索引 | 建标量索引 |
| 混合查询慢 | 自动策略没选对 | hint 手动指定 |
9. 内存相关
OceanBase 4.3.5 BP3 之后可用 GV$OB_VECTOR_MEMORY 视图:
1 | SELECT b.zone, a.svr_ip, a.svr_port, a.tenant_id, |
10. 向量索引创建
通过 __all_virtual_ddl_diagnose_info 确认索引创建状态,gv$session_longops 查看创建中的索引。通过 real_parallelism 关键字确认创建向量索引的并行度。
收集 traceid 示例:
1 | obdiag gather log --from='2026-03-16 21:00:00' --to='2026-03-17 17:00:00' --scope=all --grep='YB420A80D369-000649E8EDEED23D-0-0' |
写在最后
这份指南来自多个真实 PoC 项目的经验总结。如果这份指南对你有帮助,请转发给同样需要”向量检索”的同事和朋友~

最后附上 PoC 经验总结前两篇文章:


添加 OB 社区小助手,进入技术交流群

