欢迎光临
我们一直在努力

RAG(一):第一性原理看懂向量数据库 Milvus

目录

    • 一、先把几个词对齐
    • 二、第一性原理:为什么会出现向量库
    • 三、Milvus 里一张表到底长什么样
    • 四、一次查询实际走哪几步
    • 五、索引:用可接受的错,换掉全表扫描
    • 六、最小例子:把心智模型写成 API
    • 七、对日常工作意味着什么
    • 八、可带走的结论

在这里插入图片描述

官方文档:Milvus Docs Schema:Schema Explained 索引选型:Index Selection 过滤检索:Filtered Search

关系库擅长回答「id = 1001」「status = 已支付」。RAG、以图搜图、相似客诉一上来问的是另一件事:这两段内容像不像。等号解不了「像不像」,于是工程上把内容变成向量,把「像」定义成几何距离。Milvus 做的不是把 MySQL 换皮,而是把「存向量、建近似索引、带过滤地找近邻」做成数据库能力。

本文面向已经熟悉 MySQL / Elasticsearch 的工程师。阅读前请先有「一张表有行有列」的直觉;不必先会训练模型。

本文覆盖:为什么需要向量、Milvus 里一张 Collection 装什么、一次检索怎么走、FLAT / IVF / HNSW 差在哪、什么时候不该上 Milvus。不覆盖:K8s 集群运维、Embedding 模型训练、完整 RAG 工程、与 Pinecone / Weaviate 的跑分对比。

读完后,你应能:用一句话解释向量检索;对照关系表说清 Collection / Entity / 向量字段 / 标量字段;判断自己的场景是该用倒排、该用向量,还是两者一起用。


一、先把几个词对齐

同事嘴里的「向量库」「embedding」「ANN」「召回」经常指同一条链上的不同环节。

说法它实际更接近什么
Embedding 把文本/图/音频编成一组固定长度的浮点数;模型做这件事,不是 Milvus
向量(Vector) Embedding 的产物,一条高维坐标
相似度 / 距离 两条向量近不近:常见 COSINE / IP / L2
ANN Approximate Nearest Neighbor,用近似换速度,不保证每次都是全局最近
Collection Milvus 里最接近「表」的东西
Entity 一行:主键 + 向量 + 可选标量字段
标量字段 标题、类目、时间、状态等,用来过滤,不是拿来算「语义近」

一句话记:模型负责把世界编成坐标;Milvus 负责把海量坐标存住,并在可接受误差内尽快找出邻居。


二、第一性原理:为什么会出现向量库

从约束往回推,只需要四步。

  • 业务对象对不上等号。 客诉原文、商品图、通话录音无法用 WHERE title = 用户原话 稳定命中。
  • 「意思」可以先变成几何。 同一套 Embedding 下,语义近的样本在空间里也近。检索从「匹配字符串」变成「找近邻」。
  • 精确近邻太贵。 百万级、768 维,对每条查询做全量距离是 O(N × dim)。延迟和 CPU 会先把系统打穿。
  • 所以必须近似。 先缩小候选,再在候选里算距离。这就是 ANN 索引。Milvus 把索引、过滤、持久化、加载做成了数据库,而不是你在进程里抱着一份 Faiss 文件。
  • 向量检索很像 Elasticsearch 的倒排:都是「先建结构,再少扫一点」。差别是倒排按 词项 切 文档;向量索引按 空间位置 组织 点。

    在这里插入图片描述

    图 1:左边查「苹果」只回含这个词的文档;右边在向量空间里,「水果」「iPhone」也可能落在近邻圈内。

    常见情况这样处理。真正麻烦的是边界情况:法律条款、订单号、错误码仍然要精确匹配。向量库补的是语义召回,不替代主键和倒排。


    三、Milvus 里一张表到底长什么样

    Collection 可以按关系库来对照,但有两处不能混。

    关系库直觉Milvus不能想当然的地方
    Database / Table Database / Collection Collection 必须有至少一个向量字段
    Row Entity 一行里的 向量维度写进 Schema,事后不能偷偷改 dim
    Column Field 字段分向量字段和标量字段,职责不同
    Primary key Primary key 仍要有;用来定位、去重、点查
    B+Tree / 倒排 Vector index + 可选标量索引 向量字段必须建索引才能 load 后检索
    WHERE Filter 表达式 过滤的是标量;「像不像」走向量

    一条知识库切片在 Schema 里通常是:

    字段类型直觉干什么
    id 主键 回表、去重、删除
    embedding FLOAT_VECTOR,如 dim=768 语义近邻
    title / chunk VARCHAR 展示原文;也可做少量标量条件
    biz_type / created_at 标量 biz_type == "faq" and created_at > …

    Milvus 不替你算 Embedding。写入前,业务侧先用同一套模型把文本变成 dim 维向量;查询时必须用同一套模型编查询句。模型和库换一套,近邻关系就作废。


    四、一次查询实际走哪几步

    用户打的是自然语言。Milvus 收到的是已经编好的 query vector。中间那一跳在应用进程里,不在数据库内核里。

    在这里插入图片描述

    图 2:Embedding 在库外完成;Milvus 内部并行的是「在索引里找近邻」和「用标量把范围收窄」,最后吐 TopK 实体。

    对调用方,官方约定的顺序是固定的:建表 → 写入 → 建索引 → load → search。跳过 load,集合数据还在,但查询节点读不到。

    #mermaid-svg-yLFT7XdUQ7yqY6oM{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yLFT7XdUQ7yqY6oM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yLFT7XdUQ7yqY6oM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yLFT7XdUQ7yqY6oM .error-icon{fill:#552222;}#mermaid-svg-yLFT7XdUQ7yqY6oM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yLFT7XdUQ7yqY6oM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yLFT7XdUQ7yqY6oM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yLFT7XdUQ7yqY6oM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yLFT7XdUQ7yqY6oM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yLFT7XdUQ7yqY6oM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yLFT7XdUQ7yqY6oM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yLFT7XdUQ7yqY6oM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yLFT7XdUQ7yqY6oM .marker.cross{stroke:#333333;}#mermaid-svg-yLFT7XdUQ7yqY6oM svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yLFT7XdUQ7yqY6oM p{margin:0;}#mermaid-svg-yLFT7XdUQ7yqY6oM .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-yLFT7XdUQ7yqY6oM .cluster-label text{fill:#333;}#mermaid-svg-yLFT7XdUQ7yqY6oM .cluster-label span{color:#333;}#mermaid-svg-yLFT7XdUQ7yqY6oM .cluster-label span p{background-color:transparent;}#mermaid-svg-yLFT7XdUQ7yqY6oM .label text,#mermaid-svg-yLFT7XdUQ7yqY6oM span{fill:#333;color:#333;}#mermaid-svg-yLFT7XdUQ7yqY6oM .node rect,#mermaid-svg-yLFT7XdUQ7yqY6oM .node circle,#mermaid-svg-yLFT7XdUQ7yqY6oM .node ellipse,#mermaid-svg-yLFT7XdUQ7yqY6oM .node polygon,#mermaid-svg-yLFT7XdUQ7yqY6oM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yLFT7XdUQ7yqY6oM .rough-node .label text,#mermaid-svg-yLFT7XdUQ7yqY6oM .node .label text,#mermaid-svg-yLFT7XdUQ7yqY6oM .image-shape .label,#mermaid-svg-yLFT7XdUQ7yqY6oM .icon-shape .label{text-anchor:middle;}#mermaid-svg-yLFT7XdUQ7yqY6oM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yLFT7XdUQ7yqY6oM .rough-node .label,#mermaid-svg-yLFT7XdUQ7yqY6oM .node .label,#mermaid-svg-yLFT7XdUQ7yqY6oM .image-shape .label,#mermaid-svg-yLFT7XdUQ7yqY6oM .icon-shape .label{text-align:center;}#mermaid-svg-yLFT7XdUQ7yqY6oM .node.clickable{cursor:pointer;}#mermaid-svg-yLFT7XdUQ7yqY6oM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yLFT7XdUQ7yqY6oM .arrowheadPath{fill:#333333;}#mermaid-svg-yLFT7XdUQ7yqY6oM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yLFT7XdUQ7yqY6oM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yLFT7XdUQ7yqY6oM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yLFT7XdUQ7yqY6oM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yLFT7XdUQ7yqY6oM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yLFT7XdUQ7yqY6oM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yLFT7XdUQ7yqY6oM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yLFT7XdUQ7yqY6oM .cluster text{fill:#333;}#mermaid-svg-yLFT7XdUQ7yqY6oM .cluster span{color:#333;}#mermaid-svg-yLFT7XdUQ7yqY6oM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yLFT7XdUQ7yqY6oM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yLFT7XdUQ7yqY6oM rect.text{fill:none;stroke-width:0;}#mermaid-svg-yLFT7XdUQ7yqY6oM .icon-shape,#mermaid-svg-yLFT7XdUQ7yqY6oM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yLFT7XdUQ7yqY6oM .icon-shape p,#mermaid-svg-yLFT7XdUQ7yqY6oM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yLFT7XdUQ7yqY6oM .icon-shape .label rect,#mermaid-svg-yLFT7XdUQ7yqY6oM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yLFT7XdUQ7yqY6oM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yLFT7XdUQ7yqY6oM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yLFT7XdUQ7yqY6oM :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    createCollection

    insert

    createIndex

    loadCollection

    search / query

    search 是向量近邻(可带 filter)。query 是按标量/主键取行,不算「像不像」。两者不要混着用错。

    带过滤时,请求里会同时出现「找谁像」和「只在哪一类里找」。例如:在 biz_type == "faq" 的子集里,找与用户问题最近的 5 条。过滤表达式写复杂了会拖延迟;基础用法先保持「少量标量条件 + TopK」。


    五、索引:用可接受的错,换掉全表扫描

    没有索引时,FLAT 对每个实体算一次距离,召回最高,规模一大就不可用。ANN 的第一性原理就一句话:先丢掉大部分点,再在剩下的点里精算。

    在这里插入图片描述

    图 3:左边是精确但线性的;右边先圈候选集。丢掉的点里可能有真正的最近邻,这就是召回率代价。

    基础阶段先记住三种,不必一次调完参数。

    索引在干什么更适合你要接受什么
    FLAT 全量算距离 数据量小、要对齐召回上限 查询随 N 变慢
    IVF_FLAT 先分簇,查询只探 nprobe 个簇 内存紧、带过滤较常见 nprobe 越大越准也越慢
    HNSW 分层近邻图上跳 内存够、要高召回低延迟 图占内存;过滤比例极高时图会碎
    AUTOINDEX 让 Milvus 选默认实现 入门、还没开始调参 极致调优时仍要换成显式类型

    度量也要和模型一致,不能凭感觉挑。

    metric_type近意味着常见坑
    L2 欧氏距离小 向量没归一化时,模长大的样本会「看起来更远」
    IP 内积大 模长会进分数;未归一化时和「夹角相似」不是一回事
    COSINE 夹角小(已按方向比) 多数文本 Embedding 入门先用它,少踩模长坑

    入门默认:AUTOINDEX + COSINE。要自己上 IVF / HNSW 时,先锁死同一份评测集再改 nprobe / ef,不要在线上凭一次手感调。


    六、最小例子:把心智模型写成 API

    环境:Python 3、pymilvus 与 Milvus 2.x 兼容版本、实例地址 http://localhost:19530。向量用假数据占位;换成真实模型时,写入和查询必须同一套。

    from pymilvus import MilvusClient
    import random

    dim = 8
    client = MilvusClient("http://localhost:19530")
    client.create_collection("faq", dimension=dim, metric_type="COSINE")

    rows = [
    {"id": 1, "vector": [random.random() for _ in range(dim)], "title": "如何退货"},
    {"id": 2, "vector": [random.random() for _ in range(dim)], "title": "运费谁承担"},
    ]
    client.insert("faq", rows)

    hits = client.search(
    collection_name="faq",
    data=[[random.random() for _ in range(dim)]],
    limit=2,
    output_fields=["title"],
    )
    print(hits)

    做完后你应看到:返回列表里每条带 id、distance 和 title。distance 在 COSINE 下越大越像(具体是否已转成相似度,以当前 SDK 文档为准,读字段时不要和 L2 混比)。

    小数据量下这套没问题。放到百万级,假向量和没评测的索引参数都会让「看起来能搜」变成「召回随机」。


    七、对日常工作意味着什么

    上 Milvus 之前,先问三件事。

  • 召回要不要「意思近」。 只要精确字段、精确词,倒排或关系库就够。
  • Embedding 是否稳定。 模型、维度、是否归一化一变,旧索引全部作废,要整库重编。
  • 过滤是不是一等公民。 真实请求几乎都是「某类目 / 某租户 / 某时间窗里的相似」。Schema 里要提前留标量字段,而不是只存一条裸向量。
  • 适合的典型场景:问答知识库、相似工单、以图搜图、推荐里的向量召回层。不适合:订单状态机、库存扣减、强一致转账;也不适合把未切片的整本手册塞成一条 768 维向量还指望定位到某一段。

    仍未完全搞清、需要你自己用数据验证的是:过滤比例很高时,HNSW 是否比 IVF 更差。官方和社区都指出图在高过滤比下会碎;这不是口诀能代替评测的点。


    八、可带走的结论

  • 向量库解决的是「像不像」,前提是 Embedding 把语义编进几何;库本身不理解中文。
  • Milvus 的基本对象是 Collection = 主键 + 向量 + 标量;检索 = ANN 近邻 ∩ 标量过滤。
  • 索引用召回换延迟。入门 AUTOINDEX + COSINE;规模起来再在评测集上比较 FLAT / IVF / HNSW。
  • 整理完毕,完结撒花~🌻

    赞(0)
    未经允许不得转载:171主机测评 » RAG(一):第一性原理看懂向量数据库 Milvus
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址