七、混合检索(Hybrid Search)
原理
同时执行多种检索方式(向量语义检索 + BM25 全文检索),各取 TopK,然后融合排序,最终返回 TopN。
代码示例
from pymilvus import WeightedRanker, RRFRanker
results = client.hybrid_search(
collection_name="articles",
reqs=[
{"data": [query_vector], "anns_field": "vector", "limit": 10}, # 向量检索
{"data": [query_text], "anns_field": "sparse", "limit": 10}, # BM25全文检索
],
rerank=WeightedRanker(0.7, 0.3), # 加权融合:向量70%,BM25 30%
limit=5, # 最终返回5条
)
为什么需要混合检索
纯向量检索:抓"语义相近",但可能漏掉关键词精确匹配
纯BM25检索:抓"关键词命中",但理解不了语义
混合检索:两者结合,既不漏语义也不漏关键词
融合策略
| 策略 |
原理 |
特点 |
| WeightedRanker |
按分数加权 |
需要调权重,调优后效果更好 |
| RRFRanker |
按排名倒数融合 |
不需要调参,通用场景首选 |
前提条件
建表时需定义稠密向量和稀疏向量两个字段:
client.create_collection(
collection_name="articles",
fields=[
{"name": "id", "type": "INT64", "is_primary": True},
{"name": "vector", "type": "FLOAT_VECTOR", "dim": 384}, # 稠密向量
{"name": "sparse", "type": "SPARSE_FLOAT_VECTOR"}, # 稀疏向量(BM25)
{"name": "text", "type": "VARCHAR", "max_length": 1000},
]
)
八、面试常见问题
基础概念
| 问题 |
简答 |
| Milvus 是什么? |
向量数据库,存储高维向量,做 ANN 相似度检索 |
| 为什么不用 MySQL 存向量? |
MySQL 没有 ANN 索引,百万级数据全表计算就卡死 |
| 支持哪些相似度计算? |
L2(欧氏距离)、IP(内积)、COSINE(余弦相似度) |
| 检索结果是精确的吗? |
不是,ANN 是近似算法,召回率通常 95%+ |
索引与性能
| 问题 |
简答 |
| 有哪些索引类型? |
FLAT、IVF、HNSW、DISKANN |
| HNSW 为什么快? |
多层跳表,O(logN) 复杂度 |
| IVF 的 nlist/nprobe? |
nlist 分桶数,nprobe 搜索桶数,nprobe 越大召回越高但越慢 |
| 查询慢怎么优化? |
检查索引是否生效、调 nprobe/ef 参数、数据量是否超内存 |
架构与原理
| 问题 |
简答 |
| 整体架构? |
存算分离,Coordinator 调度 + Worker 计算 + 对象存储持久化 + etcd 元数据 |
| 写入流程? |
写消息日志 → 消费写入对象存储 → Growing Segment(可查)→ Seal 后建索引 |
| 插入后多久能查到? |
近实时,写入后进入 Growing Segment 立即可查,但无索引是暴力扫描 |
实战场景
| 问题 |
简答 |
| 向量维度怎么选? |
取决于 Embedding 模型,BERT 768维,OpenAI 1536维 |
| 百亿级怎么处理? |
分 Collection/Partition + DISKANN + 分布式集群 |
| Embedding 模型升级维度变了? |
新建 Collection,重新生成全量向量,迁移数据 |
| 能替代 MySQL 吗? |
不能,无 JOIN/聚合,通常 Milvus + MySQL 配合 |
九、选型建议
| 场景 |
推荐 |
| 本地开发/测试 |
Milvus Lite(pip install,无需 Docker) |
| 生产单机 |
Docker 单机版 |
| 生产集群 |
Milvus 分布式 + etcd + MinIO |
| 云托管 |
Zilliz Cloud(Milvus 官方云服务) |
与其他向量数据库对比
| 数据库 |
类型 |
特点 |
| Milvus |
开源 |
国产、生态全、十亿级 |
| Pinecone |
云托管 |
闭源SaaS、开箱即用 |
| pgvector |
PG 插件 |
SQL 原生、迁移成本低 |
| Qdrant |
开源 |
Rust、性能好 |
| Weaviate |
开源 |
内置向量化模块 |
| Chroma |
开源 |
轻量级、适合原型 |
| FAISS |
库 |
Meta 开源、纯计算库、极致性能 |
十、Milvus vs MySQL
Milvus 和 MySQL 是互补关系,不是替代关系。核心区别在于设计目标不同:MySQL 解决结构化数据的精确查询和事务,Milvus 解决高维向量的近似最近邻检索。
事务与一致性
|
MySQL |
Milvus |
| 事务 |
ACID,BEGIN/COMMIT/ROLLBACK |
无事务,每条插入独立 |
| 两阶段提交 |
支持(XA 协议) |
不支持 |
| 回滚 |
支持 |
不支持 |
| 隔离级别 |
4 种(RU/RC/RR/Serializable) |
无隔离级别概念 |
| 一致性 |
强一致 |
最终一致(写入后近实时可查) |
并发与锁
|
MySQL |
Milvus |
| 行锁 |
共享锁/排他锁 |
无锁 |
| 表锁 |
支持 |
无 |
| 写写并发 |
行锁串行化 |
消息队列串行化(天然有序) |
| 读写并发 |
MVCC + 锁 |
读写分离,互不阻塞 |
| 死锁 |
可能发生 |
不可能 |
Milvus 不需要锁的原因:追加写入 + segment 不可变。写入追加到消息队列,天然有序;读取操作的是已 Seal 的不可变 segment,和写入互不冲突。不存在"同一个位置两个人同时改"的情况。
数据更新
|
MySQL |
Milvus |
| 按主键更新 |
UPDATE t SET x=1 WHERE id=1 |
upsert(id=1, ...) 删除旧+插入新 |
| 按条件批量更新 |
UPDATE t SET x=1 WHERE ... |
❌ 不支持 |
| 部分字段更新 |
UPDATE t SET category='new' |
❌ 必须提供完整数据(含向量) |
| 自增/计算 |
UPDATE t SET count=count+1 |
❌ 不支持 |
Milvus 的 upsert 本质是删除旧记录 + 插入新记录,不是原地修改。频繁 upsert 会产生碎片,影响查询性能。
查询能力
|
MySQL |
Milvus |
| 精确匹配 |
WHERE id=1 |
✅ 标量过滤 |
| 向量相似度搜索 |
❌ |
✅ ANN 检索 |
| JOIN |
✅ |
❌ |
| GROUP BY / 聚合 |
✅ |
❌ |
| 子查询 |
✅ |
❌ |
| 全文检索 |
需插件 |
✅ 内置 BM25 |
数据持久化与索引加载
|
MySQL |
Milvus |
| 数据存储 |
磁盘(B+树) |
对象存储(S3/MinIO) |
| 写入持久性 |
WAL + redo log |
消息队列 + 对象存储 |
| 索引位置 |
磁盘(按需读页到内存) |
HNSW/IVF 全在内存,DISKANN 在磁盘 |
| 重启恢复 |
从 WAL/redo log 恢复 |
从对象存储重新加载索引到内存 |
生产架构建议
实际生产中通常 MySQL + Milvus 配合使用:
用户请求
├── MySQL:业务数据、事务、JOIN、聚合
│ ├── 用户表、订单表
│ └── 按 id 查业务详情
│
└── Milvus:向量检索
├── 文章/商品向量
└── 相似度搜索 → 拿到 id 列表 → 回 MySQL 查详情
典型流程:
1. Milvus 向量检索 → 得到 TopK 的 id 列表
2. MySQL SELECT * FROM articles WHERE id IN (...) → 查业务字段
3. 合并返回给前端
十一、Segment 机制
Segment 是 Milvus 数据管理的最小单元,类似 MySQL 的"页"但粒度更大。
Collection(表)
├── Segment 1(已 Seal,建了 HNSW 索引)→ 内存中可查
├── Segment 2(已 Seal,建了 HNSW 索引)→ 内存中可查
└── Segment 3(Growing,还在写入)→ 内存中可查(暴力扫描)
Segment 生命周期
插入数据 → 进入 Growing Segment(可变,追加写入)
│
│ 写满阈值(默认约 512MB)
↓
Seal(冻结,不可变)→ 后台建索引(HNSW/IVF)
│
↓
加载到内存 → 可用索引快速查询
两种 Segment
|
Growing(成长中) |
Sealed(已封存) |
| 状态 |
可变,可追加写入 |
不可变,冻结 |
| 索引 |
无,暴力扫描 |
有(HNSW/IVF 等) |
| 查询 |
能查,但慢(遍历) |
能查,快(走索引) |
| 在哪 |
内存 |
索引在内存,数据在磁盘 |
为什么要分 Segment
1. 增量建索引
新写入 10 万条 → 只给这个新 Segment 建索引
不用重建整个 Collection 的索引
2. 并行查询
查询时并行扫描所有 Segment → 合并结果
Segment 1 找到 [A, B, C]
Segment 2 找到 [D, E, F]
合并排序 → 返回 TopK
3. 内存管理
可以单独 load/unload 某些 Segment
而不是全量加载或全量卸载
和 MySQL 对比
|
MySQL |
Milvus |
| 数据单元 |
页(16KB) |
Segment(~512MB) |
| 写入方式 |
原地修改(B+树) |
追加到 Growing Segment |
| 冻结机制 |
无 |
Seal 后不可变 |
| 建索引 |
全表建 |
按 Segment 增量建 |