从本地 Demo 到生产级检索:Milvus 学习笔记(2)

书接上回:从本地 Demo 到生产级检索:Milvus 学习笔记(1)

目录

  • 插入删除数据
    • [插入实体(Insert Entities)](#插入实体(Insert Entities))
    • Upsert实体
    • [删除实体(Delete Entities)](#删除实体(Delete Entities))
  • 索引
  • 搜索
    • [基础 ANN 搜索(Basic ANN Search)](#基础 ANN 搜索(Basic ANN Search))
    • [过滤搜索(Filtered Search)](#过滤搜索(Filtered Search))
    • [范围搜索(Range Search)](#范围搜索(Range Search))
    • [分组搜索(Grouping Search)](#分组搜索(Grouping Search))
    • [主键搜索(Primary-Key Search)](#主键搜索(Primary-Key Search))
    • [多向量混合搜索(Multi-Vector Hybrid Search)](#多向量混合搜索(Multi-Vector Hybrid Search))
      • [创建包含多个向量字段的 Collection](#创建包含多个向量字段的 Collection)
      • [执行混合搜索(Perform Hybrid Search)](#执行混合搜索(Perform Hybrid Search))
      • [高级用法(Advanced usage)------在混合搜索中临时设置时区](#高级用法(Advanced usage)——在混合搜索中临时设置时区)
    • [Query 查询](#Query 查询)
      • [使用 Get(Use Get)](#使用 Get(Use Get))
      • [使用 Query(Use Query)](#使用 Query(Use Query))
      • [使用 QueryIterator](#使用 QueryIterator)
    • 标量过滤规则
      • [基础操作符(Basic Operators)](#基础操作符(Basic Operators))
        • [比较操作符(Comparison operators)](#比较操作符(Comparison operators))
        • [范围操作符(Range operators)](#范围操作符(Range operators))
        • [算术运算符(Arithmetic Operators)](#算术运算符(Arithmetic Operators))
        • [逻辑运算符(Logical Operators)](#逻辑运算符(Logical Operators))
        • [IS NULL 和 IS NOT NULL 操作符](#IS NULL 和 IS NOT NULL 操作符)
      • [过滤模板(Filter Templating)](#过滤模板(Filter Templating))
      • [JSON 运算符(JSON Operators)](#JSON 运算符(JSON Operators))
      • [ARRAY 运算符(ARRAY Operators)](#ARRAY 运算符(ARRAY Operators))
      • [结构数组运算符(StructArray Operators)](#结构数组运算符(StructArray Operators))
      • [随机采样(Random Sampling)](#随机采样(Random Sampling))
      • [Geometry Operators](#Geometry Operators)
    • [全文检索(Full Text Search)](#全文检索(Full Text Search))
      • FAQ
      • 理解BM25
        • [BM25 本质是什么?](#BM25 本质是什么?)
        • [BM25 怎么计算相关性?](#BM25 怎么计算相关性?)
        • [那为什么不用 Embedding?](#那为什么不用 Embedding?)
        • 稀疏向量是什么?
        • [BM25 为什么生成稀疏向量?](#BM25 为什么生成稀疏向量?)
    • [文本匹配(Text Match)](#文本匹配(Text Match))
    • [文本高亮(Text Highlighter)](#文本高亮(Text Highlighter))

插入删除数据

插入实体(Insert Entities)

如果你在集合创建后新增了字段,并且在插入数据时没有为这些新字段指定值,Milvus 会自动填充:

  • 已定义默认值的字段 → 使用默认值
  • 未定义默认值的字段 → 填充为 NULL

标准的 insert 操作不会检查主键是否重复。

如果插入的数据主键已经存在,Milvus 会创建一个具有相同主键的新实体,这可能会导致:

  • 数据重复
  • 应用层逻辑异常

如果你希望:

  • 更新已有实体
  • 避免重复数据

请使用 upsert 操作。

当向 Collection 插入 Entity 时:

  • 只有包含 Schema 中定义的全部字段的数据才能成功插入
  • 插入后的 Entity 默认按插入顺序进入 _default 分区(Partition)

如果指定的 Partition 已存在,也可以在插入请求中指定 Partition 名称,将 Entity 插入到对应分区。

Milvus 还支持 动态字段(Dynamic Field),用于提升 Collection 的可扩展性。

当启用动态字段后,你可以向 Collection 插入 Schema 中未定义的字段。这些字段会以键值对(key-value)的形式存储到系统保留字段 $meta 中。

在插入数据之前,你需要按照 Schema 的定义,将数据组织为:

  • 一个字典列表(list of dictionaries)
  • 每个字典表示一个 Entity
  • 每个 Entity 必须包含 Schema 中定义的所有字段

如果 Collection 启用了动态字段,那么字典中还可以包含 Schema 未定义的字段。

本示例将向一个通过 quick-setup 方式创建的 Collection 中插入数据。

该 Collection 仅包含两个字段:

  • id
  • vector

此外,该 Collection 启用了动态字段,因此示例中的 Entity 还包含了一个 Schema 中未定义的字段 color

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

data=[
    {"id": 0, "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], "color": "pink_8682"},
    {"id": 1, "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104], "color": "red_7025"},
    {"id": 2, "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592], "color": "orange_6781"},
    {"id": 3, "vector": [0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345], "color": "pink_9298"},
    {"id": 4, "vector": [0.4452349528804562, -0.8757026943054742, 0.8220779437047674, 0.46406290649483184, 0.30337481143159106], "color": "red_4794"},
    {"id": 5, "vector": [0.985825131989184, -0.8144651566660419, 0.6299267002202009, 0.1206906911183383, -0.1446277761879955], "color": "yellow_4222"},
    {"id": 6, "vector": [0.8371977790571115, -0.015764369584852833, -0.31062937026679327, -0.562666951622192, -0.8984947637863987], "color": "red_9392"},
    {"id": 7, "vector": [-0.33445148015177995, -0.2567135004164067, 0.8987539745369246, 0.9402995886420709, 0.5378064918413052], "color": "grey_8510"},
    {"id": 8, "vector": [0.39524717779832685, 0.4000257286739164, -0.5890507376891594, -0.8650502298996872, -0.6140360785406336], "color": "white_9381"},
    {"id": 9, "vector": [0.5718280481994695, 0.24070317428066512, -0.3737913482606834, -0.06726932177492717, -0.6980531615588608], "color": "purple_4976"}
]

res = client.insert(
    collection_name="quick_setup",
    data=data
)

print(res)

# 输出
# {'insert_count': 10, 'ids': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}

你也可以将实体插入到指定的 Partition 中。

下面的代码示例假设你的 Collection 中已经存在一个名为 partitionA 的分区。

python 复制代码
data=[
    {"id": 10, "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], "color": "pink_8682"},
    {"id": 11, "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104], "color": "red_7025"},
    {"id": 12, "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592], "color": "orange_6781"},
    {"id": 13, "vector": [0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345], "color": "pink_9298"},
    {"id": 14, "vector": [0.4452349528804562, -0.8757026943054742, 0.8220779437047674, 0.46406290649483184, 0.30337481143159106], "color": "red_4794"},
    {"id": 15, "vector": [0.985825131989184, -0.8144651566660419, 0.6299267002202009, 0.1206906911183383, -0.1446277761879955], "color": "yellow_4222"},
    {"id": 16, "vector": [0.8371977790571115, -0.015764369584852833, -0.31062937026679327, -0.562666951622192, -0.8984947637863987], "color": "red_9392"},
    {"id": 17, "vector": [-0.33445148015177995, -0.2567135004164067, 0.8987539745369246, 0.9402995886420709, 0.5378064918413052], "color": "grey_8510"},
    {"id": 18, "vector": [0.39524717779832685, 0.4000257286739164, -0.5890507376891594, -0.8650502298996872, -0.6140360785406336], "color": "white_9381"},
    {"id": 19, "vector": [0.5718280481994695, 0.24070317428066512, -0.3737913482606834, -0.06726932177492717, -0.6980531615588608], "color": "purple_4976"}
]

res = client.insert(
    collection_name="quick_setup",
    partition_name="partitionA",
    data=data
)

print(res)

# 输出
# {'insert_count': 10, 'ids': [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]}

Upsert实体

upsert 操作为在集合中插入或更新实体提供了一种便捷方式。

使用 upsert 来插入一个新的实体,或者更新一个已有实体,具体取决于 upsert 请求中提供的主键是否已经存在于 Collection 中。如果未找到该主键,则会执行插入(insert)操作;否则,将执行更新(update)操作。

Milvus 中的 upsert 支持 override(覆盖)模式和 merge(合并)模式。

工作在 override 模式下的 upsert 请求,本质上结合了 insert 和 delete 两种操作。当 Milvus 收到针对已存在实体的 upsert 请求时,会插入请求负载中携带的数据,同时删除原主键对应的已有实体。

如果目标 Collection 的主键字段启用了 autoid,那么 Milvus 会在插入前,为请求负载中的数据自动生成新的主键。

对于启用了 nullable 的字段,如果不需要更新这些字段,可以在 upsert 请求中省略它们。

你也可以通过 partial_update 标志,使 upsert 请求工作在 merge 模式下。这允许你仅在请求负载中包含需要更新的字段。

要执行 merge 更新,需要在 upsert 请求中将 partial_update 设置为 True,并同时提供主键以及需要更新的新字段值。

收到此类请求后,Milvus 会先以强一致性(strong consistency)执行查询以获取目标实体,然后根据请求中的数据更新字段值,接着插入修改后的数据,最后删除原主键对应的旧实体。

对于 ARRAY 字段,merge 模式支持 ARRAY_APPEND 和 ARRAY_REMOVE 两个操作符。这些操作符允许你在无需事先查询实体当前值的情况下,直接向已有 ARRAY 字段追加元素或删除匹配元素。详情请参考 Upsert ARRAY fields with partial-update operators。

在使用 merge 功能之前,需要注意以下一些特殊行为。下面的示例假设你有一个 Collection,其中包含两个标量字段 title 和 issue,以及主键 id 和一个名为 vector 的向量字段。

假设 issue 字段允许为 null。在对这些字段执行 upsert 时,需要注意:

如果在 upsert 请求中省略了 issue 字段,并且禁用了 partial_update,那么 issue 字段会被更新为 null,而不是保留原有值。

如果希望保留 issue 字段的原值,你需要启用 partial_update 并省略 issue 字段,或者在 upsert 请求中显式携带 issue 字段原有的值。

假设示例 Collection 启用了 dynamic field,并且某实体动态字段中的键值对类似于:

json 复制代码
{"author": "John", "year": 2020, "tags": ["fiction"]}

当你对 author、year、tags 等 key 执行 upsert,或者新增其他 key 时,需要注意:

如果在 partial_update 禁用的情况下执行 upsert,则默认行为为 override。这意味着动态字段的值会被请求中所有未在 Schema 中定义的字段及其对应值整体覆盖。

例如,如果请求中的数据为:

json 复制代码
{"author": "Jane", "genre": "fantasy"}

那么目标实体动态字段中的键值对将被更新为上述内容。

如果在 partial_update 启用的情况下执行 upsert,则默认行为为 merge。这意味着动态字段的值会与请求中所有未在 Schema 中定义的字段及其对应值进行合并。

例如,如果请求中的数据为:

json 复制代码
{"author": "John", "year": 2020, "tags": ["fiction"]}

那么 upsert 后,目标实体动态字段中的键值对将变为:

json 复制代码
{"author": "John", "year": 2020, "tags": ["fiction"], "genre": "fantasy"}

假设示例 Collection 中存在一个 Schema 定义的 JSON 字段 extras,并且其中某实体的 JSON 数据类似于:

json 复制代码
{"author": "John", "year": 2020, "tags": ["fiction"]}

当你使用修改后的 JSON 数据对 extras 字段执行 upsert 时,需要注意 JSON 字段会被视为一个整体,你无法选择性地更新其中某个 key。换句话说,JSON 字段不支持 merge 模式下的 upsert。

在 merge 模式下,ARRAY 字段支持 ARRAY_APPEND 和 ARRAY_REMOVE 两种 partial-update 操作符。当你希望在不替换整个数组值的情况下,向已有 ARRAY 字段添加元素或删除匹配元素时,可以使用这些操作符。

基于上述内容,需要遵循以下限制与约束:

  • upsert 请求必须始终包含目标实体的主键。
  • 目标 Collection 必须已经加载并可用于查询。
  • 请求中指定的所有字段必须存在于目标 Collection 的 Schema 中。
  • 请求中所有字段的值必须与 Schema 中定义的数据类型一致。
  • 对于通过函数派生生成的字段,Milvus 会在 upsert 过程中移除该派生字段,以便重新计算。

本节将向名为 my_collection 的 Collection 中执行 upsert 操作。该 Collection 包含四个字段:idvectortitleissue。其中,id 字段为主键字段,而 titleissue 字段为标量字段。

如果 Collection 中已经存在以下三个实体,则它们会被 upsert 请求中的数据覆盖。

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

data=[
    {
        "id": 0, 
        "vector": [-0.619954382375778, 0.4479436794798608, -0.17493894838751745, -0.4248030059917294, -0.8648452746018911],
        "title": "Artificial Intelligence in Real Life", 
        "issue": "vol.12"
    }, {
        "id": 1, 
        "vector": [0.4762662251462588, -0.6942502138717026, -0.4490002642657902, -0.628696575798281, 0.9660395877041965], 
        "title": "Hollow Man", 
        "issue": "vol.19"
    }, {
        "id": 2, 
        "vector": [-0.8864122635045097, 0.9260170474445351, 0.801326976181461, 0.6383943392381306, 0.7563037341572827], 
        "title": "Treasure Hunt in Missouri", 
        "issue": "vol.12"
    }
]

res = client.upsert(
    collection_name='my_collection',
    data=data
)

print(res)

# Output
# {'upsert_count': 3}

你也可以向指定 Partition 中执行 upsert 操作。以下代码示例假设你的 Collection 中已经存在一个名为 PartitionA 的分区。

如果该分区中已经存在以下三个实体,则它们会被请求中的数据覆盖。

python 复制代码
data=[
    {
        "id": 10, 
        "vector": [0.06998888224297328, 0.8582816610326578, -0.9657938677934292, 0.6527905683627726, -0.8668460657158576], 
        "title": "Layour Design Reference", 
        "issue": "vol.34"
    },
    {
        "id": 11, 
        "vector": [0.6060703043917468, -0.3765080534566074, -0.7710758854987239, 0.36993888322346136, 0.5507513364206531], 
        "title": "Doraemon and His Friends", 
        "issue": "vol.2"
    },
    {
        "id": 12, 
        "vector": [-0.9041813104515337, -0.9610546012461163, 0.20033003106083358, 0.11842506351635174, 0.8327356724591011], 
        "title": "Pikkachu and Pokemon", 
        "issue": "vol.12"
    },
]

res = client.upsert(
    collection_name="my_collection",
    data=data,
    partition_name="partitionA"
)

print(res)

# Output
# {'upsert_count': 3}

下面的代码示例展示了如何通过 partial update 执行 upsert。你只需要提供需要更新的字段及其新值,并显式开启 partial update 标志。

在下面的示例中,upsert 请求中指定实体的 issue 字段将被更新为请求中的值。

在 merge 模式下执行 upsert 时,需要确保请求中的所有实体具有相同的字段集合。假设存在两个或更多待 upsert 的实体,如下面代码所示,它们必须包含一致的字段,否则可能会导致错误并影响数据一致性。

python 复制代码
data=[
    {
        "id": 1,
        "issue": "vol.14"
    },
    {
        "id": 2, 
        "issue": "vol.7"
    }
]

res = client.upsert(
    collection_name="my_collection",
    data=data,
    partial_update=True
)

print(res)

# Output
# {'upsert_count': 2}

在 partial-update 操作符引入之前,更新 ARRAY 字段中的部分内容需要采用客户端 read-modify-write 流程:先查询已有数组,在应用代码中修改数组,再通过 upsert 写回整个替换后的数组值。partial-update 操作符允许你仅发送需要追加或删除的元素,从而减少客户端逻辑,并避免在 upsert 前额外执行一次读取操作。

假设主键为 1 的实体已经包含:

python 复制代码
tags = ["new", "trial"]

在没有 partial-update 操作符的情况下,如果要向数组中添加 "premium",则需要 upsert 整个替换后的数组:

python 复制代码
client.upsert(
    collection_name="users",
    data=[{"pk": 1, "tags": ["new", "trial", "premium"]}],
    partial_update=True,
)

使用 ARRAY_APPEND 时,只需发送需要追加的元素即可:

python 复制代码
from pymilvus import FieldOp

client.upsert(
    collection_name="users",
    data=[{"pk": 1, "tags": ["premium"]}],
    field_ops={"tags": FieldOp.array_append()},
)

通过 field_ops 为字段附加任意一个操作符时,会隐式启用 partial-update 语义。因此,你无需再额外传递 partial_update=True

限制:

  • 请求负载中的值必须与目标 ARRAY 字段的 element_type 一致。例如,如果目标字段为 ARRAY<VARCHAR>,则请求中的值必须为字符串类型。
  • ARRAY_APPENDARRAY_REMOVE 支持的 ARRAY 元素类型包括:BOOLINT8INT16INT32INT64FLOATDOUBLEVARCHAR
  • 在执行 ARRAY_APPEND 后,结果数组的长度不能超过字段定义的 max_capacity
  • 针对同一实体的并发 upsert 请求在多个请求之间并不保证原子性。如果两个请求同时更新同一个 ARRAY 字段,后写入的数据可能会覆盖先写入的数据。如果你需要保留所有并发修改,应在应用层进行协调控制。

以下示例使用一个简单的 users 集合,该集合包含:

  • 主键字段 pk
  • 类型为 ARRAY<VARCHAR>tags 字段
  • 向量字段 embedding

示例首先插入两个实体及其初始 tags 值,然后使用 ARRAY_APPENDARRAY_REMOVE 展示每个操作符如何改变存储在数组中的数据。

python 复制代码
from pymilvus import DataType, FieldOp, MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

# 1. 创建一个包含 ARRAY<VARCHAR> 字段的 collection
schema = client.create_schema(enable_dynamic_field=False)
schema.add_field("pk", DataType.INT64, is_primary=True)
schema.add_field("embedding", DataType.FLOAT_VECTOR, dim=5)
schema.add_field(
    "tags",
    DataType.ARRAY,
    element_type=DataType.VARCHAR,
    max_capacity=8,
    max_length=32,
)

index_params = client.prepare_index_params()
index_params.add_index(
    field_name="embedding",
    index_type="AUTOINDEX",
    metric_type="L2",
)

client.create_collection(
    collection_name="users",
    schema=schema,
    index_params=index_params
)

# 2. 初始化插入两个实体
client.insert(
    collection_name="users",
    data=[
        {"pk": 1, "embedding": [0.1, 0.2, 0.3, 0.4, 0.5], "tags": ["new"]},
        {"pk": 2, "embedding": [0.6, 0.7, 0.8, 0.9, 1.0], "tags": ["new", "trial"]},
    ],
)

不读取 ARRAY 原值直接追加 tags

python 复制代码
client.upsert(
    collection_name="users",
    data=[
        {"pk": 1, "tags": ["premium", "vip"]},
        {"pk": 2, "tags": ["premium"]},
    ],
    field_ops={"tags": FieldOp.array_append()},
)

查询结果:

python 复制代码
res = client.query(
    collection_name="users",
    filter="pk in [1, 2]",
    output_fields=["pk", "tags"],
)
print(res)

示例输出:

text 复制代码
data: [
  "{'pk': 1, 'tags': ['new', 'premium', 'vip']}",
  "{'pk': 2, 'tags': ['new', 'trial', 'premium']}"
]

在不替换整个 ARRAY 的情况下删除匹配元素

python 复制代码
client.upsert(
    collection_name="users",
    data=[
        {"pk": 1, "tags": ["new"]},
        {"pk": 2, "tags": ["trial"]},
    ],
    field_ops={"tags": FieldOp.array_remove()},
)

再次查询:

python 复制代码
res = client.query(
    collection_name="users",
    filter="pk in [1, 2]",
    output_fields=["pk", "tags"],
)
print(res)

示例输出:

text 复制代码
data: [
  "{'pk': 1, 'tags': ['premium', 'vip']}",
  "{'pk': 2, 'tags': ['new', 'premium']}"
]

删除实体(Delete Entities)

你可以通过过滤条件或主键来删除不再需要的实体。

当你需要批量删除具有某些共同属性的实体时,可以使用过滤表达式(filter expressions)。

下面的示例代码使用 in 操作符,批量删除 color 字段值为 redpurple 的所有实体。你也可以使用其他操作符来构建满足需求的过滤表达式。关于过滤表达式的更多信息,请参考 Filtering Explained。

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

res = client.delete(
    collection_name="quick_setup",
    filter="color in ['red_7025', 'purple_4976]"
)

print(res)

# 输出
# {'delete_count': 2}

在大多数情况下,主键可以唯一标识一个实体。你可以通过在 delete 请求中指定主键来删除实体。

下面的示例代码展示了如何删除主键为 18 和 19 的两个实体。

python 复制代码
res = client.delete(
    collection_name="quick_setup",
    ids=[18, 19]
)

print(res)

# 输出
# {'delete_count': 2}

你也可以删除存储在指定分区中的实体。以下示例代码假设你的 Collection 中存在一个名为 partitionA 的分区。

python 复制代码
res = client.delete(
    collection_name="quick_setup",
    ids=[18, 19],
    partition_name="partitionA"
)

print(res)

# 输出
# {'delete_count': 2}

索引

索引是在数据之上构建的一种附加结构,其内部结构取决于所使用的近似最近邻(approximate nearest neighbor, ANN)搜索算法。索引可以加速搜索,但在搜索过程中会带来额外的预处理时间、空间以及内存开销。此外,使用索引通常会降低召回率(尽管影响通常较小,但仍然需要注意)。因此,本文将介绍如何在尽量降低索引使用成本的同时,最大化其收益。

在 Milvus 中,索引是针对字段(field)级别的,不同数据类型所支持的索引类型也不同。作为专业的向量数据库,Milvus 重点优化向量检索与标量过滤性能,因此提供了多种索引类型。

下表列出了字段数据类型与可用索引类型之间的映射关系。

字段数据类型 适用索引类型
FLOAT_VECTOR / FLOAT16_VECTOR / BFLOAT16_VECTOR / INT8_VECTOR FLAT, IVF_FLAT, IVF_SQ8, IVF_PQ, IVF_RABITQ, HNSW, HNSW_SQ, HNSW_PQ, HNSW_PRQ, DISKANN, SCANN, AISAQ, GPU_CAGRA, GPU_IVF_FLAT, GPU_IVF_PQ, GPU_BRUTE_FORCE
BINARY_VECTOR BIN_FLAT, BIN_IVF_FLAT, MINHASH_LSH
SPARSE_FLOAT_VECTOR SPARSE_INVERTED_INDEX
VARCHAR INVERTED(推荐), BITMAP, Trie
BOOL BITMAP(推荐), INVERTED
INT8 / INT16 / INT32 / INT64 INVERTED, STL_SORT
FLOAT / DOUBLE INVERTED
ARRAY(BOOL / INT8/16/32/64 / VARCHAR 元素类型) BITMAP(推荐)
ARRAY(BOOL / INT8/16/32/64 / FLOAT / DOUBLE / VARCHAR 元素类型) INVERTED
JSON INVERTED

选择合适的索引类型对向量检索性能和资源消耗影响很大。在选择向量字段索引类型时,需要综合考虑多个因素,包括底层数据结构、内存使用情况以及性能需求。

如图所示,在 Milvus 中,一个索引类型由三个核心组件构成:数据结构(data structure)、量化(quantization)以及重排序器(refiner)。其中 quantization 与 refiner 是可选组件,但由于其在"收益大于成本"的情况下效果明显,因此被广泛使用。

在构建索引时,Milvus 会结合所选的数据结构与量化方法,从而确定一个最优的扩展比例(expansion rate)。在查询阶段,系统首先检索 topK × expansion rate 个候选向量,然后使用 refiner 对这些候选向量进行更高精度的距离计算,最终返回最准确的 topK 结果。这种混合方式通过将计算密集的精化过程限制在候选子集上,实现了速度与精度之间的平衡。

数据结构构成索引的基础层。常见类型包括:

  1. 倒排文件(Inverted File,IVF)

    IVF 系列索引通过基于中心点(centroid-based partitioning)的方式将向量聚类到不同的桶(bucket)中。通常可以认为:如果某个桶的中心点与查询向量足够接近,那么该桶中的向量也大概率与查询向量接近。在这一假设下,Milvus 只会扫描与查询向量相近的桶中的向量嵌入,而不会遍历整个数据集。这种策略在保证可接受精度的同时显著降低计算成本。

    这种索引数据结构非常适合需要高吞吐的大规模数据集场景。

  2. 基于图的结构(Graph-based structure)

    基于图的向量检索结构,例如 Hierarchical Navigable Small World(HNSW),会构建一个分层图结构,其中每个向量都连接到其最近邻。查询过程在该层级结构中进行,从上层的粗粒度层开始逐层向下搜索,从而实现对数级别的高效检索复杂度。

    这种索引结构在高维空间以及低延迟查询场景中表现更优。

量化(Quantization)

量化通过更粗粒度的表示方式来减少内存占用与计算成本:

标量量化(Scalar Quantization,例如 SQ8)可以将每个向量维度压缩为 1 个字节(8-bit),相比 32 位浮点数可减少约 75% 的内存占用,同时仍然保持较好的精度。

乘积量化(Product Quantization,PQ)则通过将向量拆分为多个子向量,并利用码本(codebook-based clustering)进行编码,实现更高的压缩比(例如 4--32 倍),但会带来一定程度的召回率下降,因此更适用于内存受限的环境。

重排序器(Refiner)

量化本质上是有损的。为了维持召回率,量化阶段通常会返回比实际 top-K 更多的候选结果,然后由重排序器使用更高精度进一步筛选出最终 top-K,从而提升召回效果。

例如,FP32 重排序器会对量化阶段返回的候选结果重新计算距离,并使用 FP32 精度而非量化值进行计算。

这种机制对于需要在检索效率与精度之间权衡的应用非常关键,例如语义搜索或推荐系统,因为微小的距离差异可能显著影响结果质量。

这种分层架构------通过数据结构进行粗筛、通过量化提升计算效率、通过重排序进行精度优化------使 Milvus 能够自适应地优化准确率与性能之间的权衡。

在评估性能时,需要在构建时间、每秒查询数(QPS)以及召回率之间进行平衡。一般规则如下:

  • 基于图的索引类型通常在 QPS 方面优于 IVF 系列。

  • IVF 系列特别适用于 topK 较大的场景(例如超过 2000)。

  • 在相同压缩率下,PQ 通常比 SQ 具有更好的召回率,但 SQ 通常速度更快。

  • 使用硬盘存储部分索引(例如 DiskANN)可以支持大规模数据,但也可能引入 IOPS 瓶颈。

容量(Capacity)

容量通常涉及数据规模与可用内存之间的关系。在考虑容量时,可以参考以下原则:

  • 如果四分之一的原始数据可以放入内存,可以考虑 DiskANN,以获得更稳定的延迟。

  • 如果所有原始数据都可以放入内存,可以考虑基于内存的索引类型以及 mmap。

  • 可以结合使用量化索引类型与 mmap,在精度与容量之间进行权衡。

mmap 并不总是最优解。当大部分数据都在磁盘上时,DiskANN 能提供更好的延迟表现。

召回率(Recall)

召回率通常与过滤比例(filter ratio)相关,即在搜索前被过滤掉的数据比例。在考虑召回率时,可以参考以下规则:

  • 当过滤比例小于 85% 时,基于图的索引类型优于 IVF 系列。

  • 当过滤比例在 85% 到 95% 之间时,使用 IVF 系列更合适。

  • 当过滤比例超过 98% 时,使用暴力搜索(FLAT)可获得最精确的结果。

上述规则并非绝对,建议通过不同索引类型进行调优以确定最优方案。

性能(Performance)

搜索性能通常与 top-K 相关,即一次查询返回的结果数量。在考虑性能时,可以参考以下规则:

  • 对于较小 top-K(例如 2000)且需要高召回率的搜索,基于图的索引优于 IVF 系列。

  • 当 top-K 相对于向量总量较大时,IVF 系列优于图索引。

  • 当 top-K 中等且过滤比例较高时,IVF 系列通常是更优选择。

下表是在选择索引类型时可参考的决策矩阵:

场景 推荐索引 说明
原始数据可完全放入内存 HNSW, IVF + Refinement 小 k / 高召回优先使用 HNSW
原始数据在磁盘(SSD) DiskANN 低延迟查询的最佳选择
原始数据在磁盘且 RAM 有限 IVFPQ / SQ + mmap 在内存与磁盘访问间取得平衡
高过滤比例(>95%) Brute-Force(FLAT) 候选集很小,避免索引开销
大 k(≥数据集 1%) IVF 聚类剪枝减少计算量
极高召回率(>99%) Brute-Force(FLAT)+ GPU ------

内存使用估算(Memory usage estimation)

索引的内存消耗主要受三个因素影响:数据结构、通过量化带来的压缩率,以及所使用的重排序器(refiner)。一般来说,基于图的索引通常具有更高的内存占用,因为图结构(例如 HNSW)本身会带来明显的"每个向量额外空间开销"。相比之下,IVF 及其变种在内存使用上更高效,因为其每个向量的附加结构开销较小。然而,像 DiskANN 这样的高级技术可以将索引的一部分(例如图结构或 refiner)放到磁盘上,从而在保持性能的同时降低内存占用。

具体来说,索引的内存使用可以按如下方式计算:

IVF 索引内存使用(IVF index memory usage)

IVF 索引通过将数据划分为多个簇(clusters)来在内存效率与检索性能之间取得平衡。下面以 100 万个 128 维向量为例,说明 IVF 及其变体的内存消耗构成。

  1. 计算质心(centroids)占用的内存

    IVF 系列索引通过基于质心的方式将向量划分到不同桶中。每个质心以原始向量形式存储在索引中。

    当将向量划分为 2000 个簇时,内存占用计算如下:

    复制代码
    2,000 clusters × 128 dimensions × 4 bytes = 1.0 MB
  2. 计算簇分配(cluster assignments)内存

    每个向量会被分配到一个簇,并以整数 ID 存储。对于 2000 个簇,2 字节整数已经足够表示:

    复制代码
    1,000,000 vectors × 2 bytes = 2.0 MB
  3. 量化带来的压缩

    IVF 变体通常使用 PQ 或 SQ8,其内存占用如下估算:

    使用 PQ(8 个子量化器)

    复制代码
    1,000,000 vectors × 8 bytes = 8.0 MB

    使用 SQ8

    复制代码
    1,000,000 vectors × 128 dimensions × 1 byte = 128 MB

    不同配置下的内存估算表

    配置 内存估算 总内存
    IVF-PQ(无重排序) 1.0 MB + 2.0 MB + 8.0 MB 11.0 MB
    IVF-PQ + 10% 原始向量重排序 1.0 MB + 2.0 MB + 8.0 MB + 51.2 MB 62.2 MB
    IVF-SQ8(无重排序) 1.0 MB + 2.0 MB + 128 MB 131.0 MB
    IVF-FLAT(完整原始向量) 1.0 MB + 2.0 MB + 512 MB 515.0 MB
  4. 计算重排序(refinement)开销

    IVF 变体通常会配合 refiner 对候选结果进行重排序。例如:

    topK = 10,扩展率(expansion rate)= 5:

    复制代码
    10 × 5 = 50 candidates

    每个候选向量为 128 维 FP32:

    复制代码
    50 × 128 × 4 bytes = 25.6 KB

基于图的索引内存使用(Graph-based index memory usage)

基于图的索引类型(如 HNSW)需要较大的内存来同时存储图结构和原始向量嵌入。下面以 100 万个 128 维向量、使用 HNSW 索引为例,详细拆解其内存消耗。

  1. 计算图结构所使用的内存

    在 HNSW 中,每个向量都会维护与其邻居节点的连接关系。假设图的度(每个节点的边数)为 32,则内存消耗计算如下:

    复制代码
    1,000,000 vectors × 32 links × 4 bytes(32 位整数存储) = 128 MB
  2. 计算原始向量嵌入所使用的内存

    存储未压缩 FP32 向量所消耗的内存可以按如下方式计算:

    text 复制代码
    1,000,000 vectors × 128 dimensions × 4 bytes = 512 MB

    当你使用 HNSW 对 100 万个 128 维向量嵌入进行索引时,总内存占用为:

    text 复制代码
    128 MB(图结构) + 512 MB(向量) = 640 MB
  3. 计算量化带来的压缩

    量化可以减少向量大小。例如,使用 PQ(8 个子量化器,每个向量 8 bytes)会带来显著压缩。压缩后的向量嵌入内存消耗可以按如下方式计算:

    text 复制代码
    1,000,000 vectors × 8 bytes = 8 MB

    与原始向量嵌入相比,这实现了 64 倍的压缩率,同时 HNSWPQ 索引类型的总内存占用为:

    text 复制代码
    128 MB(图结构) + 8 MB(压缩向量) = 136 MB
  4. 计算重排序开销(refinement overhead)

    重排序(refinement),例如使用原始向量进行重新排序,会临时将高精度数据加载到内存中。对于一次检索 top 10 结果、扩展率为 5 的搜索,其重排序开销可以估算如下:

    text 复制代码
    10 (topK) × 5 (expansion rate) = 50 candidates
    50 candidates × 128 dimensions × 4 bytes = 25.6 KB

虽然 IVF 和基于图的索引可以通过量化优化内存使用,但内存映射文件(memory-mapped files,mmap)和 DiskANN 用于解决数据集超过可用随机访问内存(RAM)的场景。

DiskANN 是一种基于 Vamana 图的索引结构,在搜索过程中通过连接数据点实现高效导航,同时应用 PQ 来减少向量大小,从而实现向量之间的快速近似距离计算。

Vamana 图存储在磁盘上,使 DiskANN 能够处理那些无法完全放入内存的大规模数据集。这对于十亿级数据点的数据集尤其有用。

**内存映射(Mmap)**允许直接访问磁盘上的大文件,使 Milvus 能够同时在内存和硬盘上存储索引与数据。这种方式通过根据访问频率减少 I/O 调用开销来优化 I/O 操作,从而在不显著影响搜索性能的情况下扩展集合存储容量。

具体来说,你可以将某些字段的原始数据配置为 memory-map,而不是完全加载到内存中。这样可以在不担心内存问题的情况下直接访问这些字段,并扩展集合容量。

浮点向量索引(Floating Vector Indexes)

FLAT(无索引结构的线性暴力搜索方法)

FLAT 索引是用于浮点向量索引与搜索的最简单、最直接的方法之一。它采用暴力(brute-force)方式:在查询时,将每个查询向量直接与数据集中每一个向量进行比较,而不进行任何高级预处理或数据结构构建。这种方式保证了结果的准确性,因为所有可能的匹配都会被计算,从而实现 100% 的召回率。

然而,这种全量扫描方式也带来了明显的代价。FLAT 索引是所有索引选项中速度最慢的,因为它对每一次查询都要执行一次完整的数据集扫描。因此,它不适用于大规模数据环境下对性能有要求的场景。FLAT 索引的主要优势在于其简单性与可靠性,因为它不需要训练,也不需要复杂的参数配置。

要在 Milvus 中的向量字段上构建 FLAT 索引,需要使用 add_index() 方法,并指定索引的 index_typemetric_type 参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",  # 需要建立索引的向量字段名称
    index_type="FLAT",                   # 要创建的索引类型
    index_name="vector_index",           # 索引名称
    metric_type="L2",                    # 用于计算相似度的距离度量方式
    params={}                            # FLAT 不需要额外参数
)

在该配置中:

  • index_type:要构建的索引类型。本例中设置为 FLAT。
  • metric_type:用于计算向量距离的方法。支持 COSINE、L2 和 IP 等。详情可参考 Metric Types。
  • params:FLAT 索引不需要额外参数。

一旦索引参数配置完成,可以通过 create_index() 方法直接创建索引,或者在 create_collection 时传入索引参数进行创建。

当索引构建完成并插入实体后,就可以在该索引上进行相似度搜索。

python 复制代码
res = MilvusClient.search(
    collection_name="your_collection_name",  # 集合名称
    anns_field="vector_field",               # 向量字段名称
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],         # 查询向量
    limit=3,                                  # 返回 TopK 结果数量
    search_params={"params": {}}             # FLAT 不需要额外参数
)

对于 FLAT 索引,在索引创建阶段和搜索阶段都不需要任何额外参数。

IVF_FLAT

IVF_FLAT 索引是一种能够提升浮点向量检索性能的索引算法。

该索引类型非常适合大规模数据集场景,尤其是在满足以下条件时表现最佳:需要快速查询响应与较高精度,同时数据可以通过聚类减少搜索空间,并且系统具备足够内存来存储聚类信息。

术语 IVF_FLAT 代表 Inverted File Flat(倒排文件-平面索引),它体现了对浮点向量进行索引与搜索的双层结构方法:

  • Inverted File(IVF)

    表示使用 k-means 聚类将向量空间划分为多个可管理的区域。每个簇(cluster)由一个质心(centroid)表示,该质心作为该簇内向量的参考点。

  • Flat(平面结构)

    表示在每个簇内部,向量以原始形式(未压缩、未量化)存储,即采用平坦结构,从而支持精确的距离计算。

这种索引方式可以加速检索过程,但存在一个潜在问题:距离查询向量最近的真实向量,可能不在最近质心所对应的簇中,而是位于其他簇内。

为了解决该问题,IVF_FLAT 提供了两个可调超参数:

  • nlist:使用 k-means 算法划分的簇数量
  • nprobe:搜索时需要探查的簇数量

如果将 nprobe 从 1 调整为 3,则结果如下:

通过增大 nprobe,可以在搜索时覆盖更多簇,从而提高找到真实最近向量的概率,即使它不在最近质心所在的簇中。然而,这也会带来代价:需要评估更多候选数据,从而增加搜索时间。

要在 Milvus 中的向量字段上构建 IVF_FLAT 索引,需要使用 add_index() 方法,并指定 index_typemetric_type 以及其他参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",  # 需要建立索引的向量字段名称
    index_type="IVF_FLAT",                # 要创建的索引类型
    index_name="vector_index",            # 索引名称
    metric_type="L2",                     # 用于计算相似度的距离度量
    params={
        "nlist": 64,                      # 索引中聚类的数量
    }
)

在该配置中:

  • index_type:要构建的索引类型。本例中设置为 IVF_FLAT
  • metric_type:用于计算向量距离的方法。支持 COSINE、L2 和 IP 等,详情参考 Metric Types
  • params:索引构建的额外配置项
  • nlist:将数据划分的簇数量

一旦索引构建完成并插入实体之后,就可以在该索引上执行相似度搜索。

python 复制代码
search_params = {
    "params": {
        "nprobe": 10,  # 要搜索的簇数量
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",  # 集合名称
    anns_field="vector_field",
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],         # 查询向量
    limit=3,                                  # 返回 TopK 结果数量
    search_params=search_params
)

在该配置中:

  • params:在索引上进行搜索时的额外配置项
  • nprobe:搜索的簇数量

下表列出了在构建索引时 params 中可配置的参数:

参数 描述 取值范围 调优建议
nlist 使用 k-means 算法在索引构建过程中创建的簇数量。每个簇由一个质心表示,并存储一组向量。增大该参数会减少每个簇中的向量数量,从而形成更小、更聚焦的分区。 类型:整数 范围:1, 65536 默认值:128 较大的 nlist 可以通过构建更精细的簇来提升召回率,但会增加索引构建时间。建议根据数据集规模和资源情况进行优化。通常建议范围为:32, 4096

下表列出了在搜索索引时 search_params.params 中可配置的参数:

参数 描述 取值范围 调优建议
nprobe 要搜索的候选簇数量。该值越大,搜索范围越广,通过扩大搜索空间可以提升召回率,但会增加查询延迟。 类型:整数 范围:1, nlist 默认值:8 增大该值可以提升召回率,但可能降低搜索速度。建议将 nprobe 与 nlist 成比例调整以平衡速度与精度。

通常情况下,建议将该参数设置在范围:

1, nlist

IVF 里的 IVF 到底是什么?

IVF 里的 "Inverted File" ≠ 传统意义上的"倒排索引(Inverted Index)"

IVF = Inverted File

但这里的 "Inverted File" 指的是一种**"分桶索引结构"**,不是文本检索里的倒排索引。

更准确的理解是:

IVF = "把向量空间先分桶(partition/bucket),再只查相关桶"的索引结构

Step 1:K-means 聚类

把所有向量分成 N 个 cluster:

复制代码
cluster_1 → 一堆向量
cluster_2 → 一堆向量
...
cluster_n → 一堆向量

每个 cluster 本质就是:

一个"空间区域(region)"

Step 2:建立"倒排表"(关键点)

IVF 的"Inverted File"来自这一点:

复制代码
cluster_id → 向量列表(posting list)

也就是:

cluster 存的内容
0 向量 A, B, C
1 向量 D, E
2 向量 F, G

这和搜索引擎的倒排索引结构"很像":

  • 词 → 文档列表
  • cluster → 向量列表

所以叫 Inverted File

IVF_SQ8

IVF_SQ8 是一种基于**量化(Quantization)**的索引算法,专门用于解决大规模向量相似度搜索问题。与全量遍历(Exhaustive Search)相比,该索引类型能够在显著降低内存占用的同时,实现更快的搜索速度。

IVF_SQ8 索引由两个核心组件构成:

  • 倒排文件(IVF,Inverted File)

    将数据组织为多个聚类,使搜索算法能够只关注与查询最相关的部分向量集合。

  • 标量量化(SQ8,Scalar Quantization)

    将向量压缩成更加紧凑的形式,在保留足够精度用于相似度计算的前提下,大幅降低内存消耗。

IVF 可以理解为给一本书建立目录。

与其逐页翻阅整本书(对应遍历所有向量),不如先查目录(聚类),快速定位到最相关的章节(向量集合)。

在向量检索场景中,数据会被划分为多个聚类,查询时只会搜索与查询向量最接近的少数几个聚类,而不是整个数据集。

工作原理:

  1. 聚类(Clustering)

    使用 K-Means 等聚类算法,将向量数据集划分为指定数量的聚类。

    每个聚类都会生成一个中心点(Centroid),作为该聚类的代表向量。

  2. 向量分配(Assignment)

    将每个向量分配到距离自己最近的聚类中心。

  3. 构建倒排索引(Inverted Index)

    建立索引结构,将:

    text 复制代码
    聚类中心
        ↓
    属于该聚类的向量列表

    进行映射。

  4. 搜索(Search)

    当执行最近邻搜索时:

    1. 首先计算查询向量与各个聚类中心之间的距离;
    2. 选出最有可能包含目标向量的若干聚类;
    3. 仅在这些聚类内部进行进一步搜索。

    这样能够大幅减少需要比较的向量数量。

Scalar Quantization(标量量化) 是一种通过使用更小的数据表示来压缩高维向量的技术。

SQ8 使用 8 位整数(int8) 替代传统的 32 位浮点数(float32) 来存储向量每个维度的值,从而显著降低存储开销。(必然会丢失精度)

SQ8 的工作流程:

  1. 确定取值范围(Range Identification)

    首先找到向量中的:

    • 最小值(min)
    • 最大值(max)

    这两个值定义了量化区间。

  2. 归一化(Normalization)

    将向量中的值映射到 [0,1] 区间:

    normalized_value=\frac{value-min}{max-min}

    这样可以保证所有维度都被映射到统一范围,为后续压缩做好准备。

  3. 8 位压缩(8-Bit Compression)

    将归一化后的数值乘以 255(8 位整数的最大值),然后四舍五入:

    text 复制代码
    quantized_value = round(normalized_value × 255)

    最终每个维度只需要 1 个字节进行存储。

假设某个维度的值为:

text 复制代码
value = 1.2
min   = -1.7
max   = 2.3

归一化后:

text 复制代码
(1.2 - (-1.7)) / (2.3 - (-1.7))
= 2.9 / 4
= 0.725

量化:

text 复制代码
0.725 × 255
≈ 184.875
≈ 185

因此:

text 复制代码
float32: 1.2
      ↓
int8 : 185

原本需要 4 字节存储的数据,现在仅需 1 字节。

IVF_SQ8 将 IVF 和 SQ8 结合起来,实现高效的向量相似度搜索。

IVF:缩小搜索范围

数据集首先被划分为多个聚类。

查询到来时:

  1. 先与各聚类中心进行比较;
  2. 选择最相关的聚类;
  3. 仅在这些聚类中继续搜索。

这样减少了需要扫描的向量数量。

SQ8:加速距离计算

在被选中的聚类内部:

  • 向量以 8 位整数形式存储;
  • 内存占用显著降低;
  • 更多数据可以驻留在内存或 CPU Cache 中;
  • 距离计算速度更快。

要在 Milvus 中为向量字段构建 IVF_SQ8 索引,可以使用 add_index() 方法,并指定 index_typemetric_type 以及索引相关的附加参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",  # 向量字段名称
    index_type="IVF_SQ8",                 # 索引类型
    index_name="vector_index",            # 索引名称
    metric_type="L2",                     # 距离度量方式
    params={
        "nlist": 64,                      # K-Means 聚类数量
    }
)

配置说明

  • index_type :索引类型。本示例为 IVF_SQ8

  • metric_type :向量距离计算方式。支持 COSINEL2IP,具体可参考 Metric Types。

  • params:索引构建的附加配置参数。

  • nlist:K-Means 聚类数量,用于在索引构建阶段划分数据空间。

索引参数配置完成后,可以通过以下方式创建索引:

  • 直接调用 create_index()
  • 或在 create_collection() 时传入 index 参数

索引构建完成并插入数据后,即可进行相似度搜索:

python 复制代码
search_params = {
    "params": {
        "nprobe": 8,  # 搜索的聚类数量
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",  # 集合名称
    anns_field="vector_field",              # 向量字段
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],        # 查询向量
    limit=10,                                # 返回 TopK
    search_params=search_params
)

配置说明

  • params:搜索阶段的附加参数。

  • nprobe:搜索时需要探测的聚类数量。

索引参数说明(Index Params)

索引构建参数

参数 说明 取值范围 调优建议
nlist K-Means 聚类数量 1, 65536 默认 128

nlist 表示在索引构建阶段将向量空间划分为多少个聚类。

较大的 nlist:

  • 聚类更细
  • 召回率更高
  • 但建索引更慢

建议范围:

text 复制代码
32 ~ 4096

搜索参数

参数 说明 取值范围 调优建议
nprobe 搜索聚类数量 1, nlist 默认 8

nprobe 表示查询时需要扫描多少个聚类。

  • 值越大:召回率更高,但延迟更高
  • 值越小:速度更快,但可能降低召回率

nlist 与 nprobe 的关系

  • nlist:索引阶段划分多少个聚类
  • nprobe:查询阶段访问多少个聚类

两者共同影响:

  • 检索速度
  • 召回率(Recall)
量化(Quantization)

SQ8 本质上是一种有损压缩(Lossy Compression),必然会带来精度损失,它是在检索精度与存储/计算效率之间做权衡。

原始向量可能是:

python 复制代码
[0.123456, 1.987654, -0.345678]

float32 存储时:

python 复制代码
[0.123456, 1.987654, -0.345678]

量化成 SQ8 后:

python 复制代码
[120, 235, 87]

搜索时再反量化回来:

python 复制代码
[0.121, 1.991, -0.349]

已经不是原来的值了。

SQ8 压缩的是:

text 复制代码
Embedding向量

例如:

python 复制代码
[0.123, 0.876, -0.345, ...]

目的是:

  • 少占内存
  • 少占磁盘
  • 提高 Cache 命中率
  • 加快距离计算

本质:

text 复制代码
Vector -> Quantized Vector

而大模型 INT8 / INT4压缩的是:

text 复制代码
模型权重(Weights)

例如:

python 复制代码
W = [
    [0.1245, -0.4567],
    [1.2356,  0.8765]
]

量化后:

python 复制代码
W_int8 = [
    [12, -45],
    [123, 87]
]

本质:

text 复制代码
Weights -> Quantized Weights

目的是:

  • 减少显存占用
  • 降低带宽消耗
  • 提高推理速度

但数学思想是一样的:

text 复制代码
float32
    ↓
归一化
    ↓
映射到有限整数空间
    ↓
存储
    ↓
计算时反量化

都会产生:

text 复制代码
量化误差(Quantization Error)

实际上,Milvus 的 IVF_SQ8 和大模型 INT8 量化,本质上都属于同一个领域:

Quantization

即:

text 复制代码
用更少的比特表示原来的数值

例如:

类型 每个值占用
float32 32 bit
float16 16 bit
int8(SQ8) 8 bit
int4 4 bit

比特数越少:

text 复制代码
存储越省
速度越快
↓
精度越差

向量数据库里有一句非常经典的话:

text 复制代码
所有 ANN 索引都是在
Recall(召回率)
和
Performance(性能)
之间做权衡

SQ8 就是这个思想的典型代表。甚至可以把 IVF_SQ8 理解成:

text 复制代码
IVF:
减少要比较的向量数量

SQ8:
减少每个向量占用的空间

最终:
牺牲一点 Recall
换取数倍甚至数十倍性能提升

这和今天大模型从 FP32 → FP16 → INT8 → INT4 → 1.58bit 的演进思路几乎是同一套工程哲学:

text 复制代码
允许少量精度损失
换取巨大的资源收益

只是一个压缩的是 Embedding,一个压缩的是 Model Weight。

IVF_PQ

IVF_PQ 是一种基于量化的近似最近邻(ANN)索引算法,用于高维空间中的向量检索。相比部分基于图(Graph-based)的算法,它的速度可能略慢,但通常具有更低的内存占用,因此非常适合大规模数据集场景。

IVF_PQ(Inverted File with Product Quantization)是一种将索引 + 压缩结合的混合方法,用于高效的向量检索与查询。

它主要依赖两个核心组件:

  • IVF(倒排文件)
  • PQ(Product Quantization,乘积量化)

PQ(乘积量化)是一种用于高维向量的压缩方法,在大幅降低存储成本的同时,仍能支持高效的相似度计算。

PQ 工作流程:

  1. 维度拆分(Dimension Decomposition)

    将 D 维向量拆分为 m 个子向量:

    • 原始空间:D 维
    • 拆分为:m 个子空间
    • 每个子空间维度:D / m

    参数 m 控制拆分粒度,并影响压缩率。

  2. 子空间码本生成(Codebook Generation)

    在每个子空间内:

    • 使用 K-Means 聚类学习代表向量(centroids)
    • 每个子空间形成一个"码本(codebook)"

    每个码本包含:

    text 复制代码
    2^nbits 个 centroid

    例如:

    • nbits = 8
    • 则每个子空间有 256 个中心点
  3. 向量量化(Vector Quantization)

    对于每个子向量:

    • 找到其最近的 centroid
    • 用 centroid 的"索引"代替原始向量
  4. 压缩表示(Compressed Representation)

    最终一个向量被表示为:

    • m 个索引(每个子空间一个)

    称为 PQ codes。

    存储成本从:

    text 复制代码
    D × 32 bits

    降为:

    text 复制代码
    m × nbits bits

假设:

  • D = 128
  • m = 64
  • nbits = 8

原始向量:

text 复制代码
128 × 32 = 4096 bits

PQ 压缩后:

text 复制代码
64 × 8 = 512 bits

👉 压缩比:8 倍

PQ 距离计算

  1. 查询预处理

    • 将查询向量拆分为 m 个子向量
    • 对每个子空间计算其到所有 centroid 的距离
    • 形成 m 张 lookup table

    每张表大小:

    text 复制代码
    2^nbits
  2. 距离估算

    对于数据库中的 PQ 编码向量:

    • 每个子空间通过索引直接查表
    • 累加 m 个子空间的距离
    • 得到近似距离

IVF_PQ 结合两者优势,实现高效检索:

  1. IVF:粗筛

    • 将向量空间划分为多个 cluster
    • 查询时只进入最相关的少数 cluster

    👉 减少搜索范围

  2. PQ:细算

    在选中的 cluster 内:

    • 使用 PQ 压缩向量
    • 用近似距离快速计算相似度

    👉 降低计算和内存成本

要在 Milvus 中为向量字段构建 IVF_PQ 索引,可以使用 add_index() 方法,并指定 index_typemetric_type 以及索引相关参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",  # 需要建立索引的向量字段名
    index_type="IVF_PQ",                  # 索引类型
    index_name="vector_index",            # 索引名称
    metric_type="L2",                     # 相似度计算方式
    params={
        "m": 4,                           # 将每个向量划分为多少个子向量
    }
)

在上述配置中:

  • index_type

    • 要构建的索引类型。
    • 本例中设置为 IVF_PQ
  • metric_type

    • 用于计算向量间距离(相似度)的方式。

    • 支持:

      • COSINE
      • L2
      • IP
  • params

    • 索引构建时的额外配置参数。
  • m

    • 将原始向量拆分成的子向量数量。

配置完成后,可以通过:

  • 直接调用 create_index()
  • 或在 create_collection() 时传入索引参数

当索引构建完成并且数据已插入后,就可以执行相似度搜索。

python 复制代码
search_params = {
    "params": {
        "nprobe": 10,  # 搜索的聚类数量
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",  # Collection 名称
    anns_field="vector_field",               # 向量字段名
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],        # 查询向量
    limit=3,                                 # 返回 TopK 结果数
    search_params=search_params
)

在上述配置中:

  • params

    • 搜索时的额外配置参数。
  • nprobe

    • 查询时需要扫描的聚类(Cluster)数量。

以下参数可在构建索引时通过 params 配置。

IVF 参数

参数 描述 取值范围 调优建议
nlist 构建索引时,通过 K-Means 聚类生成的聚类中心数量。 类型:Integer 范围:1, 65536 默认值:128 更大的 nlist 会产生更细粒度的聚类,从而提高召回率,但会增加索引构建时间。通常推荐设置在 32, 4096 之间。

PQ 参数

参数 描述 取值范围 调优建议
m 在量化过程中,将每个高维向量拆分成的子向量数量。 类型:Integer 范围:1, 65536 默认值:无 较大的 m 能提升精度,但会增加计算复杂度和内存消耗。m 必须能够整除向量维度 D。通常推荐设置为 D/2 ,推荐范围 D/8, D
参数 描述 取值范围 调优建议
nbits 用于表示每个子向量质心索引的比特数。它决定了码本(Codebook)的大小。每个码本包含 2^nbits 个质心。 类型:Integer 范围:1, 24 默认值:8 较大的 nbits 可以获得更高精度的向量表示,但压缩率会下降。通常推荐设置在 1, 16 范围内。

例如:

  • nbits = 8
  • 每个子向量使用 8 bit 编码
  • 每个子空间的码本大小为:
text 复制代码
2^8 = 256

即包含 256 个聚类中心(Centroid)。

以下参数可在搜索时通过 search_params.params 配置。

IVF 搜索参数

参数 描述 取值范围 调优建议
nprobe 查询时参与搜索的聚类数量。 类型:Integer 范围:1, nlist 默认值:8 较大的 nprobe 可以扫描更多聚类,从而提升召回率,但会增加查询延迟。通常建议根据 nlist 按比例调整。推荐范围 1, nlist

对于 IVF_PQ,最重要的三个参数是:

text 复制代码
nlist   → 索引阶段决定聚类数量
m       → PQ压缩阶段决定子向量数量
nprobe  → 查询阶段决定扫描多少个聚类

可以简单理解为:

text 复制代码
IVF_PQ
├── IVF
│   ├── nlist   (分多少桶)
│   └── nprobe  (查多少桶)
│
└── PQ
    ├── m       (切多少段)
    └── nbits   (每段编码多少bit)

调优方向:

  • 召回率优先

    • 增大 nlist
    • 增大 nprobe
    • 增大 m
    • 增大 nbits
  • 查询速度优先

    • 减小 nprobe
    • 减小 m
  • 存储空间优先

    • 减小 m
    • 减小 nbits

其中实际线上最常调的参数通常是:

text 复制代码
nlist
nprobe

mnbits 更多是在建索引阶段确定后长期保持不变。

IVF_RABITQ

IVF_RABITQ 是 Milvus 2.6.x 引入的一种基于二值量化(Binary Quantization) 的索引算法。它能够将 FP32 向量压缩为二进制表示,在保持较好召回率(Recall)的同时,实现约 1:32 的压缩比 。此外,该索引还支持可选的 Refinement(精排)机制,通过额外存储更高精度的数据来进一步提升召回率,因此在内存资源受限的场景下,可以作为 IVF_SQ8 和 IVF_FLAT 的替代方案。

IVF_RABITQ(Inverted File with RaBitQ Quantization)结合了 IVF 和 RaBitQ 两种技术,以实现高效的向量检索和存储。

IVF(Inverted File) 通过 K-Means 聚类将向量空间划分为多个区域,每个区域由一个聚类中心(Centroid)表示。查询时,系统只需要在最相关的若干个聚类中进行搜索,而无需扫描全部数据,从而显著降低搜索开销。

RaBitQ 是一种先进的二值量化方法,由 Jianyang Gao 和 Cheng Long 在论文《RaBitQ: Quantizing High-Dimensional Vectors with a Theoretical Error Bound for Approximate Nearest Neighbor Search》中提出。

与传统量化方法不同,RaBitQ 引入了 角度信息编码(Angular Information Encoding) 的思想。它通过向量归一化来保留方向信息。在 IVF_RABITQ 中,每个数据向量会以其最近的 IVF 聚类中心作为参考进行归一化,从而提高量化精度。

RaBitQ 的核心距离近似公式如下:

其中:

  • (o_r):数据集中的向量
  • (q_r):查询向量
  • (c_o):(o_r) 对应的最近 IVF 聚类中心
  • (C(o_r,c_o))、(C_1(o_r,c_o)):预计算常量
  • (\tilde{o}):存储在索引中的二值量化向量
  • (\langle \tilde{o}, q_r-c_o\rangle):点积运算

RaBitQ 的核心思想是利用少量预计算常量和二值向量运算来近似原始距离计算,从而在保证精度的同时显著提高计算效率。

由于为二值表示,距离计算可以充分利用现代 CPU 的位运算能力。在支持 AVX-512 VPOPCNTDQ 指令的 Intel Ice Lake 及更新架构,以及 AMD Zen 4 及更新架构上,可以获得更好的性能表现。

RaBitQ 还能够与 FastScan、Random Rotation 等成熟优化技术结合使用,以进一步提升检索性能。

IVF_RABITQ 将 IVF 的聚类能力与 RaBitQ 的高压缩率结合起来:

  • 粗筛阶段(Coarse Filtering):利用 IVF 将搜索范围限制在最相关的聚类中;
  • 量化阶段(Binary Quantization):利用 RaBitQ 将向量压缩为二值表示,同时尽可能保留距离关系;
  • 精排阶段(Optional Refinement):可选地保存 SQ6、SQ8、FP16、BF16 或 FP32 格式的高精度数据,以提升最终召回率。

Milvus 底层基于 FAISS 实现 IVF_RABITQ,其对应的 Factory String 为:

启用 Refinement 时:

text 复制代码
RR({dim}),IVF{nlist},RaBitQ,Refine({refine_index})

未启用 Refinement 时:

text 复制代码
RR({dim}),IVF{nlist},RaBitQ

要在 Milvus 中为向量字段构建 IVF_RABITQ 索引,可以使用 add_index() 方法,并指定索引类型、距离度量方式以及相关构建参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",  # 待索引的向量字段
    index_type="IVF_RABITQ",              # 索引类型
    index_name="vector_index",            # 索引名称
    metric_type="L2",                     # 相似度计算方式
    params={
        "nlist": 1024,        # 聚类数量
        "refine": True,       # 是否开启精排
        "refine_type": "SQ8"  # 精排数据格式
    }
)

在上述配置中:

  • index_type

    指定要创建的索引类型。本例中为 IVF_RABITQ

  • metric_type

    指定向量距离计算方式。支持:

    • COSINE
    • L2
    • IP
  • params

    索引构建相关参数,具体说明见后文 Index Building Params

完成参数配置后,可以通过 create_index() 创建索引,或者在 create_collection() 时直接传入索引参数。

索引构建完成并插入数据后,即可执行向量相似度检索。

python 复制代码
search_params = {
    "params": {
        "nprobe": 128,        # 搜索的聚类数量
        "rbq_query_bits": 0,  # Query向量量化位数
        "refine_k": 1         # 精排候选放大倍数
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",
    anns_field="vector_field",
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],
    limit=3,
    search_params=search_params
)

其中:

  • params

    搜索阶段使用的附加参数,具体说明见后文 Index-specific Search Params

IVF_RABITQ 对 CPU 的位运算能力有较强依赖。为了获得最佳性能,建议使用支持 AVX512 VPOPCNTDQ 指令集的处理器,例如 Intel Ice Lake 及以上架构或 AMD Zen 4 及以上架构。RaBitQ 的核心距离计算会大量使用 popcount 指令,因此现代 CPU 能带来明显的性能提升。

构建索引时,可在 params 中配置以下参数。

类别 参数 说明 取值范围 调优建议
IVF nlist 索引构建时使用 K-Means 聚类生成的聚类数量。每个聚类由一个 Centroid 表示,并保存属于该聚类的向量列表。增大该参数会减少每个聚类中的向量数量,从而形成更细粒度的分区。 Type: Integer Range: 1, 65536 Default: 128 较大的 nlist 能够通过更精细的聚类提升 Recall,但会增加索引构建时间。建议根据数据规模和资源情况进行调整。通常推荐范围为 32, 4096
RaBitQ refine 是否启用 Refinement(精排)并存储高精度数据。 Type: Boolean Range: true, false Default: false 如果业务需要 0.9 以上的 Recall,建议开启。启用后能够提升检索精度,但会增加存储占用和索引构建时间。
RaBitQ refine_type refine=true 时,用于精排的数据格式。 Type: String Range: SQ6, SQ8, FP16, BF16, FP32 Default: None 上述格式按照 Recall 递增、QPS 递减、存储占用递增 的顺序排列。推荐从 SQ8 开始尝试,它通常能够在精度和资源消耗之间取得较好的平衡。

搜索时,可在 search_params.params 中配置以下参数。

类别 参数 说明 取值范围 调优建议
IVF nprobe 查询时需要搜索的聚类数量。更大的 nprobe 会扩大搜索范围,从而提升 Recall,但同时增加查询延迟。 Type: Integer Range: 1, nlist Default: 8 增大 nprobe 通常能够提高 Recall,但可能降低查询速度。建议根据 nlist 按比例调整,以平衡速度和精度。推荐范围为 1, nlist
RaBitQ rbq_query_bits 是否对 Query 向量进行额外的标量量化。如果设置为 0,则直接使用原始 Query;如果设置为 [1, 8] 之间的值,则会先使用对应位数进行标量量化。 Type: Integer Range: 0, 8 Default: 0 默认值 0 可获得最高 Recall,但性能最慢。官方建议优先测试 0、8、6 三个值,它们通常具有接近的 Recall,而 6 往往拥有最快的搜索速度。如果业务更关注 Recall,可选择更小的取值。
RaBitQ refine_k Refinement 阶段会先从 IVF_RABITQ 召回 refine_k 倍数量的候选向量,再使用更高精度的数据进行重排序,最终选出所需数量的近邻结果。 Type: Float Range: [1, float_max) Default: 1 较大的 refine_k 能提升 Recall,但会降低 QPS。建议从 1 开始测试,再逐步尝试 2、3、4、5,找到适合当前数据集的 Recall 与性能平衡点。

对于 IVF_RABITQ,最重要的几个调优参数可以概括为:

text 复制代码
构建阶段
├── nlist
├── refine
└── refine_type

搜索阶段
├── nprobe
├── rbq_query_bits
└── refine_k

经验上:

Recall 不够 → 优先增加 nprobe 或开启 refine;

QPS 不够 → 优先调整 rbq_query_bits;

Recall 已接近目标 → 再通过 refine_k 做精细优化。

HNSW

HNSW(Hierarchical Navigable Small World) 是一种基于图(Graph)的向量索引算法,特别适用于高维浮点向量检索场景。

它通常具有以下特点:

高召回率(High Recall)

低查询延迟(Low Latency)

较高的内存占用(High Memory Usage)

这是因为 HNSW 需要维护一个多层图结构来加速近邻搜索。

HNSW 可以理解为一种具有多个"缩放层级"的导航地图。最底层包含全部数据点,而上层则是从下层采样得到的部分数据点。

text 复制代码
Layer 3   ○────○────○

Layer 2   ○──○──○──○──○

Layer 1   ○─○─○─○─○─○─○

Layer 0   ○○○○○○○○○○○○○

每个节点表示一个向量,每条边表示两个向量之间的邻近关系。

在搜索过程中:

  • 上层负责快速定位目标区域(粗搜索)
  • 下层负责精细查找最近邻(精搜索)

这种结构兼顾了搜索速度和准确率。

HNSW 的搜索过程主要分为以下几个步骤:

  1. Entry Point(入口点)

    搜索从图最顶层的固定入口节点开始。

    该节点在索引构建阶段已经确定。

  2. Greedy Search(贪心搜索)

    在当前层中:

    • 计算当前节点与 Query 的距离
    • 查看所有邻居节点
    • 选择距离 Query 最近的邻居继续前进

    重复执行,直到找不到更近的节点。

    text 复制代码
    Current Node
          ↓
    Nearest Neighbor
          ↓
    Nearest Neighbor
          ↓
    Local Minimum

    此时认为已经接近目标区域。

  3. Layer Descend(层间下降)

    当当前层无法继续接近目标时:

    text 复制代码
    Layer 3
       ↓
    Layer 2
       ↓
    Layer 1
       ↓
    Layer 0

    算法会沿着预先建立的连接下降到下一层,并重复贪心搜索过程。

  4. Final Refinement(最终精排)

    到达最底层后:

    • 搜索更多候选节点
    • 计算真实距离
    • 返回最终 TopK 结果

HNSW 的性能主要由以下三个参数决定:

M:

表示图中每个节点允许建立的最大连接数(Neighbor 数量)。

text 复制代码
M = 5

      ○
    / | \
   ○  ○  ○
    \ | /
      ○

例如:

text 复制代码
M = 5

表示每个节点最多连接 5 个邻居节点。

M 越大:

  • Recall ↑
  • Accuracy ↑
  • 内存占用 ↑
  • 建索引速度 ↓

原因是图结构更稠密,搜索路径更多。

efConstruction

索引构建阶段的候选节点数量。

构建图时:

text 复制代码
新节点
   ↓
寻找 efConstruction 个候选邻居
   ↓
从中选择最佳连接

efConstruction 越大:

  • 图质量更好
  • Recall 更高
  • 建索引时间更长

ef

查询阶段的候选节点数量。

搜索时:

text 复制代码
Query
 ↓
维护 ef 个候选节点
 ↓
不断更新
 ↓
输出 TopK

ef 越大:

  • Recall ↑
  • 查询延迟 ↑

简单理解:

  • M 决定图有多密
  • efConstruction 决定图建得有多好
  • ef 决定查询时搜得有多仔细

要在 Milvus 中为向量字段创建 HNSW 索引,可以使用 add_index() 方法。

python 复制代码
from pymilvus import MilvusClient

# Prepare index building params
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",
    index_type="HNSW",
    index_name="vector_index",
    metric_type="L2",
    params={
        "M": 64,
        "efConstruction": 100
    }
)

参数说明:

参数 说明
index_type 索引类型,本例为 HNSW
metric_type 距离计算方式,支持 COSINEL2IP
M 每个节点允许连接的最大邻居数
efConstruction 建图时考虑的候选邻居数量

配置完成后,可以通过:

  • create_index()
  • create_collection()

创建索引。

索引创建完成并插入数据后,即可执行向量检索。

python 复制代码
search_params = {
    "params": {
        "ef": 10
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",
    anns_field="vector_field",
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],
    limit=10,
    search_params=search_params
)

参数说明

参数 说明
ef 查询时考虑的候选邻居数量

ef 越大,搜索范围越广,Recall 越高,但查询耗时也会增加。

构建索引时,可在 params 中配置以下参数。

参数 说明 取值范围 调优建议
M 图中每个节点允许拥有的最大连接数(Edge 数量),包括出边(Outgoing Edges)和入边(Incoming Edges)。该参数会直接影响索引构建和搜索性能。 Type: Integer Range: 2, 2048 Default: 30(每个节点最多 30 条出边和 30 条入边) 较大的 M 通常能够提升检索精度和 Recall,但会增加内存占用,同时降低索引构建和搜索速度。对于高维数据集或对 Recall 要求较高的场景,可以适当增大 M。如果更关注内存占用和查询性能,则可以适当减小 M。通常推荐范围为 5, 100
efConstruction 索引构建阶段用于选择邻居节点的候选集大小。对于每个新插入节点,系统会先评估 efConstruction 个候选邻居,再从中选出最终连接的节点,而最终建立的连接数仍受 M 限制。 Type: Integer Range: 1, int_max Default: 360 较大的 efConstruction 能够探索更多潜在连接,从而构建质量更高的图结构,提高检索精度。但同时会增加索引构建时间和构建阶段的内存消耗。如果更关注 Recall,可以适当增大该参数;如果更关注建索引速度,则可以适当减小。通常推荐范围为 50, 500

搜索时,可在 search_params.params 中配置以下参数。

参数 说明 取值范围 调优建议
ef 控制最近邻搜索过程中的搜索宽度(Search Breadth)。该参数决定搜索过程中会访问和评估多少个候选节点。ef 仅影响搜索阶段,并且只作用于 HNSW 图的最底层。 Type: Integer Range: 1, int_max Default: limit(即返回的 TopK 数量) 较大的 ef 通常能够获得更高的 Recall,因为会评估更多候选节点,但同时会增加搜索延迟。如果 Recall 是首要目标,可以适当提高该参数;如果更关注查询性能,则可以适当减小。通常推荐范围为 K, 10K(其中 K 为 TopK 值)。

HNSW 最核心的三个参数如下:

参数 作用阶段 影响
M 建索引 决定图的稠密程度
efConstruction 建索引 决定图的构建质量
ef 查询 决定搜索的深度和广度

可以简单理解为:

text 复制代码
M               → 图连得有多密
efConstruction  → 图建得有多好
ef              → 搜得有多仔细

经验上:

Recall 不够时:

优先增大 ef,其次增大 efConstruction,最后再考虑增大 M
内存占用过高时:

优先减小 M
建索引过慢时:

优先减小 efConstruction
查询过慢时:

优先减小 ef

对于大多数场景,一个比较常见的起始配置是:

python 复制代码
{
    "M": 30,
    "efConstruction": 360
}

search_params = {
    "params": {
        "ef": 100
    }
}

然后根据 Recall、QPS 和内存占用情况逐步调整。

HNSW_SQ

HNSW_SQHNSW(Hierarchical Navigable Small World) 图索引与 SQ(Scalar Quantization,标量量化) 相结合,形成一种兼顾存储效率与检索精度的向量索引方案。

与标准 HNSW 相比,HNSW_SQ 在保持较高查询速度的同时,通过压缩向量数据显著降低内存占用,但索引构建时间会略有增加。

HNSW_SQ 结合了两种技术:

  • HNSW:负责高效的图导航和近邻搜索
  • SQ:负责向量压缩,降低存储和计算成本

HNSW 会构建一个多层图结构,其中每个节点对应数据集中的一个向量。

图中的节点按照向量相似度建立连接:

text 复制代码
Vector A ───── Vector B
     │           │
     │           │
Vector C ───── Vector D

查询时,算法通过图遍历快速定位目标区域,并逐步缩小候选范围。

这种层次化结构能够显著加速高维空间中的近邻搜索。

SQ 是一种向量压缩技术,它通过减少每个维度所使用的比特数来降低存储开销。

例如:

类型 每维占用 可表示级别
FP32 32 bit 浮点数
SQ8 8 bit 256 个离散值
SQ6 6 bit 64 个离散值

其中:

SQ8 使用 8 bit 表示一个浮点值,对应 256 个量化等级。
SQ6 使用 6 bit 表示一个浮点值,对应 64 个量化等级。

量化会损失部分精度,但能够大幅降低内存占用并提升计算效率,同时保留向量的大部分结构特征。

对于追求极致查询速度和最低内存占用的场景,Milvus 引入了 SQ4U(4-bit Uniform Scalar Quantization)

SQ4U 是一种更加激进的量化方式,它将每个维度的浮点值压缩为:

text 复制代码
4 bit 无符号整数

即:

text 复制代码
0 ~ 15

总共 16 个离散等级。

SQ4U 中的 U 代表 Uniform(均匀)

传统 SQ 通常采用:

text 复制代码
Per-Dimension Quantization
(按维度量化)

即:

text 复制代码
Dimension 1 → 单独计算 min/max
Dimension 2 → 单独计算 min/max
Dimension 3 → 单独计算 min/max
...

而 SQ4U 使用:

text 复制代码
Global Uniform Quantization
(全局统一量化)

即整个向量共享同一组量化参数。

  1. Global Statistics

    系统会计算一个全局最小值 vmin 和全局值域范围 vdiff,该范围适用于向量的所有维度(或整个向量片段)。

  2. Uniform Mapping

    将全局值域划分为 16 个等间隔区间,向量中的每一个浮点值(不论属于哪个维度)都会基于这组共享参数,被映射为一个 4-bit 整数(0--15)。

性能优势:

8x 压缩比:

相比 FP32 可减少 8 倍存储,相比 SQ8 减少 2 倍存储,从而显著降低内存带宽压力(向量检索的主要瓶颈之一)。

SIMD 优化:

紧凑的数据结构使现代 CPU(AVX2 / AVX-512)可以在每个周期处理更多维度。同时,由于所有维度共享全局参数,距离计算时无需加载不同维度的 scale/offset,从而保持指令流水线高效运行。

缓存效率提升:

向量体积更小,可以在 CPU Cache 中容纳更多数据,减少内存访问延迟。

由于采用全局参数共享,SQ4U 在以下数据上表现最佳:

已归一化(Normalized)或各维度分布较一致的数据集

HNSW_SQ 将 HNSW 的图索引能力与 SQ 的压缩能力结合,用于高效的近似最近邻搜索(ANN)。整体流程如下:

  1. 数据压缩

    使用 sq_type(如 SQ6、SQ8)对向量进行量化压缩,从而降低内存占用。

    虽然会带来一定精度损失,但可以显著提升系统处理大规模数据的能力。

  2. 图构建

    使用压缩后的向量构建 HNSW 图。

    由于数据更小:

    • 图结构更紧凑
    • 搜索更快
    • 内存占用更低
  3. 候选召回

    在查询时,算法基于 HNSW 图使用压缩向量快速召回一批候选邻居。

  4. (可选)结果精排

    可通过精排步骤提升最终结果质量,相关参数如下:

    • refine:是否启用精排

      • true 时会使用更高精度重新计算距离
    • refine_type:精排所用精度(如 SQ6 / SQ8 / BF16 / FP32)

      • 精度越高,结果越准确,但内存占用越大
      • 必须高于 sq_type 的精度
    • refine_k:候选放大倍率

      • 例如 TopK=100,refine_k=2,则先取 200 个候选再重排

在 Milvus 中构建 HNSW_SQ 索引:

python 复制代码
from pymilvus import MilvusClient

index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",
    index_type="HNSW_SQ",
    index_name="vector_index",
    metric_type="L2",
    params={
        "M": 64,
        "efConstruction": 100,
        "sq_type": "SQ6",
        "refine": True,
        "refine_type": "SQ8"
    }
)

参数说明:

  • index_type:索引类型,本例为 HNSW_SQ
  • metric_type:距离计算方式(COSINE / L2 / IP)
  • M:图中每个节点最大连接数
  • efConstruction:建图时候选邻居数量
  • sq_type:向量压缩类型
  • refine:是否开启精排
  • refine_type:精排使用的精度类型

索引创建可通过:

  • create_index()
  • create_collection()

完成。

索引搜索(Search on Index)

python 复制代码
search_params = {
    "params": {
        "ef": 10,
        "refine_k": 1
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",
    anns_field="vector_field",
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],
    limit=3,
    search_params=search_params
)

参数说明

  • ef:控制查询时的搜索范围(速度 / 精度权衡)
  • refine_k:精排放大倍数

构建索引时,可在 params 中配置以下参数。

分类 参数 说明 取值范围 调优建议
HNSW M 图中每个节点允许的最大连接数(包括入边和出边)。该参数同时影响索引构建与搜索性能。 Type: Integer Range: 2, 2048 Default: 30(每个节点最多 30 条出边和 30 条入边) 较大的 M 通常会提升检索精度,但会增加内存开销,并降低索引构建与查询速度。对于高维数据或高 Recall 场景可适当增大;若更关注内存与性能,则可适当减小。推荐范围为 5, 100
HNSW efConstruction 索引构建阶段用于选择连接邻居的候选集合大小。每个新节点会评估多个候选邻居,但最终连接数仍受 M 限制。 Type: Integer Range: 1, int_max Default: 360 较大的 efConstruction 可以探索更多连接,从而提升图结构质量和检索精度,但会增加构建时间和内存消耗。若更关注精度,可适当增大;若更关注构建速度或资源限制,则可适当减小。推荐范围为 50, 500
SQ sq_type 指定用于向量压缩的标量量化方式,不同选项在压缩率与精度之间进行权衡: - SQ4U :4-bit 均匀量化,最高压缩率与最快速度 - SQ6 :6-bit 整数量化 - SQ8 :8-bit 整数量化 - BF16 :Bfloat16 格式 - FP16:16-bit 浮点格式 Type: String Range: SQ4U, SQ6, SQ8, BF16, FP16 Default: SQ8 根据业务需求选择:SQ4U 适合极致速度与内存优化;SQ6/SQ8 适合均衡场景;BF16/FP16 适合更高精度需求。
SQ refine 控制是否在搜索过程中启用精排(refinement)。精排通过对候选结果重新计算精确距离来提升排序质量。 Type: Boolean Range: true, false Default: false 若对精度要求较高且可接受一定延迟,可设为 true;若更关注性能,则保持 false。
SQ refine_type 指定精排阶段使用的数据精度。其精度必须高于 sq_type,会同时影响排序精度与内存开销。 Type: String Range: SQ6, SQ8, BF16, FP16, FP32 Default: None FP32 提供最高精度但开销最大;SQ6/SQ8 更偏压缩;BF16/FP16 提供折中方案。

搜索阶段可在 search_params.params 中配置以下参数。

分类 参数 说明 取值范围 调优建议
HNSW ef 控制最近邻搜索的范围,即搜索过程中访问并评估的节点数量。该参数仅影响搜索阶段,且只作用于图的底层。 Type: Integer Range: 1, int_max Default: limit(TopK 返回数量) 较大的 ef 通常能提升 Recall,但会增加查询时间。若追求高精度可增大;若追求低延迟可减小。推荐范围为 K, 10K
SQ refine_k 控制精排阶段的候选放大倍数,即在 TopK 基础上额外扩展多少候选进行重排。 Type: Float Range: [1, float_max) Default: 1 refine_k 越大,召回率和精度越高,但查询延迟和资源消耗也随之增加。1 表示仅对初始 TopK 进行精排。

HNSW_PQ

HNSW_PQ 结合了 HNSW(Hierarchical Navigable Small World)图索引PQ(Product Quantization,乘积量化),形成一种可在"存储占用与检索精度之间进行权衡"的高级向量索引方法。

HNSW_SQ 相比,在相同压缩级别下,HNSW_PQ 通常能够提供更高的召回率,但代价是更低的查询速度以及更长的索引构建时间。

HNSW_PQ 结合了两种技术:

  • HNSW:用于高效的图结构导航与近邻搜索
  • PQ:用于高效向量压缩与距离近似计算

HNSW 会构建一个多层图结构,每个节点对应数据集中的一个向量。

节点之间根据相似度建立连接,从而实现高效的图遍历:

text 复制代码
Vector A ───── Vector B
     │           │
     │           │
Vector C ───── Vector D

这种层级结构能够显著减少搜索范围,使高维空间中的近邻检索更高效。

PQ 是一种向量压缩技术,它将高维向量拆分为多个子向量,并对每个子空间进行量化与编码。

这种方法可以:

  • 显著降低存储占用
  • 加速距离计算
  • 通过码本(codebook)近似表示原始向量

HNSW_PQ 将 HNSW 的图索引能力与 PQ 的压缩能力结合,用于高效近似最近邻搜索(ANN)。其流程如下:

  1. 数据压缩(Data Compression)

    PQ 将每个向量拆分为多个子向量,并使用码本(codebook)进行量化编码。

    控制参数包括:

    • m:子向量数量
    • nbits:每个子向量的编码位数

    该步骤能够显著降低存储需求,但会引入一定精度损失。

  2. 图构建(Graph Construction)

    使用压缩后的向量构建 HNSW 图。

    由于向量已压缩:

    • 图结构更小
    • 内存占用更低
    • 图遍历更快

    从而加速候选检索过程。

  3. 候选召回(Candidate Retrieval)

    查询时:

    • 使用 HNSW 图进行快速导航
    • 基于压缩向量筛选候选集合

    相比暴力搜索,图结构显著减少需要计算的向量数量,从而降低延迟。

  4. (可选)结果精排(Result Refinement)

    可以通过精排提升最终结果精度,相关参数如下:

    • refine:是否启用精排

      • true 时使用更高精度重新计算距离
    • refine_type:精排使用的数据精度(如 SQ6 / SQ8 / BF16 / FP32)

      • 精度越高,结果越准确,但内存占用越大
      • 必须高于压缩阶段使用的精度
    • refine_k:候选放大倍数

      • 例如 TopK=100,refine_k=2,则先取 200 个候选再重排

在 Milvus 中创建 HNSW_PQ 索引:

python 复制代码
from pymilvus import MilvusClient

index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",
    index_type="HNSW_PQ",
    index_name="vector_index",
    metric_type="L2",
    params={
        "M": 30,
        "efConstruction": 360,
        "m": 384,
        "nbits": 8,
        "refine": True,
        "refine_type": "SQ8"
    }
)

参数说明

  • index_type:索引类型,本例为 HNSW_PQ
  • metric_type:距离计算方式(COSINE / L2 / IP)
  • M:图中每个节点最大连接数
  • efConstruction:建图阶段候选邻居数量
  • m:PQ 子向量数量
  • nbits:每个子向量的编码位数
  • refine:是否启用精排
  • refine_type:精排使用的数据精度

索引搜索(Search on Index)

python 复制代码
search_params = {
    "params": {
        "ef": 10,
        "refine_k": 1
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",
    anns_field="vector_field",
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],
    limit=3,
    search_params=search_params
)

参数说明

  • ef:控制查询阶段的搜索宽度(速度 vs 精度)
  • refine_k:精排阶段候选放大倍数

👉 总体上,HNSW_PQ 的核心权衡是:

更高 Recall(相比 SQ)

↔ 更低查询速度

↔ 更长构建时间

构建索引时,可在 params 中配置以下参数。

分类 参数 说明 取值范围 调优建议
HNSW M 图中每个节点的最大连接数(包含出边与入边)。该参数同时影响索引构建与搜索过程。 Type: Integer Range: 2, 2048 Default: 30(每个节点最多 30 条出边 + 30 条入边) 较大的 M 通常能提升精度,但会增加内存开销,并降低索引构建与查询性能。适用于高维数据或高 Recall 场景;若更关注内存与速度,可适当减小。推荐范围:5, 100
HNSW efConstruction 索引构建阶段用于选择连接邻居的候选集合大小。每个新元素会评估多个候选邻居,但最终连接数仍受 M 限制。 Type: Integer Range: 1, int_max Default: 360 较大的 efConstruction 通常能带来更高质量的图结构,从而提升检索精度,但会增加构建时间与内存消耗。若更关注精度可增大;若资源有限或更关注构建速度可减小。推荐范围:50, 500
PQ m 在量化过程中,将每个高维向量拆分为子向量的数量。 Type: Integer Range: 1, 65536 Default: None m 越大,精度越高,但计算复杂度与内存占用也随之增加。必须能整除向量维度 D。常见建议为 m = D/2,推荐范围:D/8, D
PQ nbits 用于表示每个子向量码本索引的比特数,决定码本大小。每个子空间包含 2^nbits 个中心点。例如 nbits=8 时,每个子向量使用 8-bit 编码,对应 256 个可能取值。 Type: Integer Range: 1, 24 Default: 8 nbits 越大,表达能力越强、精度更高,但压缩率下降(占用更多存储)。推荐范围:1, 16
SQ refine 是否在搜索阶段启用精排(rerank)。精排通过计算候选与查询向量的精确距离来提升最终排序精度。 Type: Boolean Range: true, false Default: false 若对精度要求较高且可接受一定延迟,可设为 true;若更关注速度则保持 false。
SQ refine_type 精排阶段使用的数据精度,必须高于压缩阶段使用的精度(由 m 和 nbits 决定)。 Type: String Range: SQ6, SQ8, BF16, FP16, FP32 Default: None FP32 精度最高但开销最大;SQ6/SQ8 压缩率更高;BF16/FP16 提供折中方案。

搜索阶段可在 search_params.params 中配置以下参数。

分类 参数 说明 取值范围 调优建议
HNSW ef 控制最近邻搜索的宽度,即在搜索过程中访问并评估的节点数量。该参数仅作用于图的底层搜索阶段。 Type: Integer Range: 1, int_max Default: limit(TopK 返回数量) ef 越大,召回率通常越高,但查询延迟也会增加。若更关注精度可增大;若更关注速度可减小。推荐范围:K, 10K
PQ refine_k 控制精排阶段的候选放大倍数,即在 TopK 基础上额外扩展多少候选进行重排。 Type: Float Range: [1, float_max) Default: 1 refine_k 越大,精度与召回率越高,但计算与延迟开销也更大。1 表示仅对初始 TopK 结果进行精排。

HNSW_PRQ

HNSW_PRQ 结合了 HNSW(Hierarchical Navigable Small World)图索引PRQ(Product Residual Quantization,乘积残差量化),是一种高级向量索引方法,可以在"索引大小与检索精度"之间进行更细粒度的权衡。

相比传统 PQ(Product Quantization),PRQ 引入了 **Residual Quantization(RQ,残差量化)**步骤,用于进一步捕获 PQ 未能表达的误差信息,因此在相同存储条件下通常能获得更高精度,或在相同精度下获得更紧凑的索引结构。

但由于增加了额外计算步骤,索引构建与搜索阶段的计算开销也会更高。

HNSW_PRQ 由两种技术组成:

  • HNSW:用于快速图结构导航与近邻检索
  • PRQ:用于多阶段向量压缩与残差建模

HNSW 会构建一个多层图结构,每个节点对应数据集中的一个向量。

节点之间基于相似度进行连接,从而实现高效的图遍历:

text 复制代码
Vector A ─── Vector B
    │           │
Vector C ─── Vector D

这种分层结构可以在高维空间中快速缩小搜索范围,从而显著提升检索效率。

PRQ 是一种多阶段压缩方法,结合了 PQ + RQ 两个步骤:

  1. Product Quantization(PQ)

    首先将高维向量拆分为多个子向量,并将每个子向量映射到码本(codebook)中的最近中心点。

    这一过程可以显著压缩数据,但会引入一定误差,因为每个子向量被单一中心近似表示。

    更多细节可参考 IVF_PQ

  2. Residual Quantization(RQ)

    在 PQ 完成后,RQ 会对"残差"进行量化:

    text 复制代码
    residual = original vector − PQ approximation

    由于残差通常较小,因此可以用更少的代价进行更精细的编码。

    参数 nrq 控制残差量化的迭代次数,用于在"压缩率 vs 精度"之间进行调节。

  3. 最终压缩表示

    PQ 与 RQ 的编码结果会被合并为最终的压缩表示:

    • PQ 提供粗粒度结构
    • RQ 补充细节误差

    两者结合使 PRQ 在不显著增加存储的情况下提升表达能力。

HNSW_PRQ 将 HNSW 的图检索能力与 PRQ 的多阶段压缩结合,整体流程如下:

  1. 数据压缩(Data Compression)

    每个向量先通过 PQ 进行粗压缩,再通过 RQ 对残差进行进一步量化。

    最终得到紧凑的编码表示。

  2. 图构建(Graph Construction)

    使用 PQ + RQ 的压缩编码构建 HNSW 图。

    由于数据已压缩:

    • 内存占用更低
    • 图结构更紧凑
    • 图遍历更快
  3. 候选检索(Candidate Retrieval)

    查询时:

    • 使用 HNSW 图进行导航
    • 基于压缩表示快速筛选候选集合

    从而显著减少需要参与计算的向量数量。

  4. (可选)结果精排(Result Refinement)

    可通过精排进一步提升精度:

    • refine:是否启用精排

      • true 时使用更高精度重新计算距离
    • refine_type:精排使用的数据精度(如 SQ6 / SQ8 / BF16 / FP32)

      • 精度越高结果越准确,但内存开销更大
      • 必须高于原始压缩精度(sq_type)
    • refine_k:候选放大倍数

      • 例如 TopK=100,refine_k=2,则重排前 200 个候选
python 复制代码
from pymilvus import MilvusClient

index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",
    index_type="HNSW_PRQ",
    index_name="vector_index",
    metric_type="L2",
    params={
        "M": 30,
        "efConstruction": 360,
        "m": 384,
        "nbits": 8,
        "nrq": 1,
        "refine": True,
        "refine_type": "SQ8"
    }
)

参数说明

  • M:图中每个节点最大连接数
  • efConstruction:构建阶段候选邻居数量
  • m:PQ 子向量数量
  • nbits:每个子向量编码位数
  • nrq:RQ 迭代次数(残差量化深度)
  • refine:是否启用精排
  • refine_type:精排使用的数据精度

搜索索引(Search on Index)

python 复制代码
search_params = {
    "params": {
        "ef": 10,
        "refine_k": 1
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",
    anns_field="vector_field",
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],
    limit=3,
    search_params=search_params
)

参数说明

  • ef:控制搜索宽度(影响速度与召回)
  • refine_k:精排阶段候选放大倍数

构建索引时,可在 params 中配置以下参数。

分类 参数 说明 取值范围 调优建议
HNSW M 图中每个节点允许的最大连接数(包含出边与入边)。该参数同时影响索引构建与搜索过程。 Type: Integer Range: 2, 2048 Default: 30(每个节点最多 30 条出边 + 30 条入边) M 越大通常精度越高,但会增加内存开销,并降低索引构建与搜索速度。适用于高维数据或高召回场景;若更关注内存与性能,可适当减小。推荐范围:5, 100
HNSW efConstruction 索引构建阶段用于选择连接邻居的候选集合大小。每个新节点会评估多个候选邻居,但最终连接数仍受 M 限制。 Type: Integer Range: 1, int_max Default: 360 较大的 efConstruction 通常能获得更优图结构与更高召回率,但会增加构建时间与内存消耗。适合对构建时间不敏感但对精度要求较高的场景;若资源有限可适当减小。推荐范围:50, 500
PRQ m 在量化过程中,将每个高维向量拆分为子向量的数量。 Type: Integer Range: 1, 65536 Default: None m 越大表示分块更细,精度更高,但计算复杂度与内存占用也更高。必须能整除向量维度 D。常见经验值为 m = D/2,推荐范围:D/8, D
PRQ nbits 用于表示每个子向量码本索引的比特数,决定码本规模(每个子空间包含 2^nbits 个中心点)。例如 nbits=8 时对应 256 个可能中心。 Type: Integer Range: 1, 24 Default: 8 nbits 越大,表达能力越强、精度越高,但压缩率下降(存储开销更大)。推荐范围:1, 16
PRQ nrq 控制 RQ(Residual Quantization)阶段使用的残差子量化器数量。 Type: Integer Range: 1, 16 Default: 2 nrq 越大,残差建模能力越强,重建精度更高,但计算与存储开销也随之增加。
SQ refine 是否在搜索阶段启用精排(reranking)。精排通过计算候选与查询向量的精确距离来提升最终排序质量。 Type: Boolean Range: true, false Default: false 若对精度要求较高且可接受一定延迟,可设为 true;若更关注速度则保持 false。
SQ refine_type 精排阶段使用的数据精度,必须高于压缩阶段使用的精度(由 m / nbits 决定)。 Type: String Range: SQ6, SQ8, BF16, FP16, FP32 Default: None FP32 精度最高但成本最大;SQ6/SQ8 更偏压缩;BF16/FP16 提供折中方案。

搜索阶段可在 search_params.params 中配置以下参数。

分类 参数 说明 取值范围 调优建议
HNSW ef 控制最近邻搜索的宽度,即在搜索过程中访问并评估的节点数量,仅作用于图的底层搜索阶段。 Type: Integer Range: 1, int_max Default: limit(TopK 返回数量) ef 越大通常召回率越高,但查询延迟也更高。适用于高精度需求场景;若更关注性能可适当减小。推荐范围:K, 10K
PRQ refine_k 控制精排阶段候选扩展倍数,即在 TopK 基础上额外扩展多少候选进行重排。 Type: Float Range: [1, float_max) Default: 1 refine_k 越大,召回率与精度越高,但搜索延迟与资源消耗也随之增加。1 表示仅对初始 TopK 结果进行精排。

DISKANN

在超大规模场景中(数据量可达数十亿甚至万亿级向量),传统基于内存的索引方法(如 HNSW、IVF_FLAT)往往会因为内存限制而难以扩展。DISKANN 提供了一种基于磁盘(Disk-based)的向量检索方案,即使在数据规模超过可用 RAM 的情况下,也能保持较高的检索精度与速度。

DISKANN 结合了两种关键技术,用于高效向量检索:

  • Vamana Graph(瓦马纳图):基于磁盘的图索引结构,用于在搜索过程中进行高效导航
  • Product Quantization (PQ):一种内存中的向量压缩方法,用于快速近似距离计算

Vamana 图是 DISKANN 的核心结构,使其能够处理超大规模数据集,因为构建过程中无需将全部数据加载到内存中。

构建流程如下:

  1. 初始随机连接

    每个数据点(向量)被表示为图中的一个节点,初始阶段这些节点通过随机方式连接,形成一个较为稠密的图结构。

    通常每个节点初始会有约 500 条边,以保证较高的连通性。

  2. 图优化(Refinement)

    在初始图基础上,会进行优化以提升检索效率,主要包括:

    (1)剪枝冗余边(Pruning)

    根据节点间距离删除低质量或冗余连接,仅保留更有价值的边。

    参数:

    • max_degree:限制每个节点的最大边数

      • 值越大 → 图越稠密 → 召回率更高
      • 但同时带来更高内存占用与查询开销

    (2)添加远距离捷径(Shortcuts)

    Vamana 会增加长距离边,将空间中较远的节点直接连接起来,从而:

    • 减少跳转次数
    • 加速图遍历
    • 提升整体搜索效率

    search_list_size该参数控制图优化阶段的搜索范围:

    • 值越大 → 搜索更充分 → 图质量更高
    • 但索引构建时间也会增加

DISKANN 使用 PQ 将高维向量压缩为更小的编码(PQ codes),并将其存储在内存中,用于快速的近似距离计算。

pq_code_budget_gb_ratio,该参数用于控制 PQ code 在内存中的占用比例。

计算方式如下:

text 复制代码
PQ Code Budget (GB) = vec_field_size_gb × pq_code_budget_gb_ratio

其中:

  • vec_field_size_gb:向量数据总大小(GB)
  • pq_code_budget_gb_ratio:用户设定的比例,用于控制 PQ code 的存储占比

该参数本质上是在:

搜索精度 ↔ 内存资源

之间做权衡。

索引构建完成后(磁盘上的 Vamana 图 + 内存中的 PQ codes),DISKANN 执行 ANN 检索流程如下:

  1. 查询与入口点(Query & Entry Point)

    查询向量进入系统后,从 Vamana 图中的一个入口节点开始搜索。

    该入口通常接近全局中心点(global centroid),即所有向量的平均位置,从而减少搜索路径长度。

  2. 邻域探索(Neighborhood Exploration)

    从当前节点出发,遍历其邻居节点(图中红色节点)。

    • 使用 PQ codes(内存中) 计算候选节点与查询向量的近似距离
    • 得到一组候选邻居集合
  3. 精确计算候选(Precise Distance Evaluation)

    从候选集合中筛选一部分最有希望的节点(图中绿色节点),并:

    • 读取磁盘中的原始向量
    • 计算精确距离

    由于涉及磁盘 IO,这一步通常较慢。

    DISKANN 使用两个关键参数控制"精度 vs 性能"的平衡:

    beam_width_ratio

    控制搜索"宽度"的比例:

    • 值越大 → 同时探索更多节点 → 召回率更高
    • 但计算开销和磁盘 IO 增加

    计算方式:

    text 复制代码
    Beam width = CPU cores × beam_width_ratio

    search_cache_budget_gb_ratio

    用于控制缓存比例:

    • 用于存储热点磁盘数据
    • 命中缓存可减少磁盘 IO
    • 提升重复查询性能
  4. 迭代搜索(Iterative Exploration)

    系统会不断重复以下过程:

    • PQ 近似评估
    • 磁盘精确计算
    • 候选集更新

    直到找到足够数量的最近邻结果。

默认情况下,Milvus 中 DISKANN 是关闭的,因为系统优先保证内存型索引在数据能够轻松放入 RAM 时的查询速度。

但如果你处理的是超大规模数据集,或者希望利用 DISKANN 的可扩展性和 SSD 优化能力,可以很方便地启用它。

找到你的 Milvus 配置文件。(关于如何定位该文件,请参考 Milvus 官方配置文档。)

找到 queryNode.enableDisk 参数,并将其设置为 true

yaml 复制代码
queryNode:
    enableDisk: true # 启用查询节点加载并使用基于磁盘的索引进行检索

为了获得最佳性能,建议将 Milvus 数据存储在高速 NVMe SSD 上。

以下是 Standalone 和 Cluster 两种部署方式的配置方法:

  1. Milvus Standalone

    将 Milvus 数据目录挂载到 NVMe SSD(位于 Milvus 容器内部)。

    可以在 docker-compose.yml 或其他容器管理工具中进行配置。

    例如,如果 NVMe SSD 挂载在 /mnt/nvme,需要在 docker-compose.ymlvolumes 部分添加:

    yaml 复制代码
    volumes:
      - /mnt/nvme/volumes/milvus:/var/lib/milvus
  2. Milvus Cluster

    需要在 QueryNode 和 IndexNode 两类容器中都挂载 NVMe SSD 数据目录。

    可以通过容器编排系统完成这一配置。

    这样可以确保查询和索引构建过程中都具备高速的读写性能。

完成上述修改后,重启 Milvus 实例以使配置生效。

此时 Milvus 将使用 DISKANN 能力来处理大规模数据,从而提供高效且可扩展的向量检索能力。

DISKANN 相关参数只能通过 Milvus 配置文件(milvus.yaml)进行设置:

yaml 复制代码
common:
  DiskIndex:
    MaxDegree: 56                  # Vamana 图的最大度
    SearchListSize: 100            # 构建图时的候选列表大小
    PQCodeBudgetGBRatio: 0.125     # PQ 编码占用大小上限(相对于原始数据)
    SearchCacheBudgetGBRatio: 0.1  # 缓存节点占比(相对于原始数据)
    BeamWidthRatio: 4              # 每轮搜索的最大 IO 请求数与 CPU 数量的比例

微调 DISKANN 参数可以让你针对特定数据集和查询负载进行调整,在速度、精度和内存使用之间取得合适平衡。

以下所有索引构建参数只能通过 Milvus 配置文件(milvus.yaml)进行配置。

Vamana:

参数 描述 取值范围 调优建议
MaxDegree 控制 Vamana 图中每个数据点的最大连接数(边数)。 Integer:1, 512 默认:56 值越大图越稠密,可能提升召回率,但增加内存占用和构建时间。一般建议:10, 100
SearchListSize 构建索引时,每个节点用于寻找最近邻的候选集合大小。算法会维护一个 search_list_size 的最优候选列表,直到无法继续优化,并从中选出 max_degree 条边。 Integer:1, int_max 默认:100 值越大越可能找到真实最近邻,图质量更高,但构建时间显著增加。必须 ≥ max_degree
SearchCacheBudgetGBRatio 控制构建索引过程中用于缓存热点图数据的内存比例。 Float:[0.0, 0.3) 默认:0.10 值越大缓存越多,减少磁盘 IO,但占用更多内存;值越小则相反。

PQ:

参数 描述 取值范围 调优建议
PQCodeBudgetGBRatio PQ 编码(压缩数据表示)占用内存相对于原始数据的比例。 Float:(0.0, 0.25] 默认:0.125 值越大精度越高但内存占用更高;值越小更省内存但精度下降。适合内存受限场景。推荐:(0.0625, 0.25]

这些参数影响 DISKANN 搜索过程中的速度、延迟和资源使用。

Vamana:

参数 描述 取值范围 调优建议
BeamWidthRatio 控制搜索并行度,即最大并行 IO 请求数与 CPU 数量的比例。 Float:1, max(128 / CPU number, 16) 默认:4.0 值越大并行度越高、搜索更快,但可能增加资源竞争。推荐:1.0, 4.0
search_list 搜索过程中维护的候选集合大小。 Integer:1, int_max 默认:100 值越大召回率更高但延迟更高。建议设置为 ≥ top_k 或略大于 top_k
ANN------Approximate Nearest Neighbor(近似最近邻)

ANN = 不精确但很快的"最近邻搜索"

和"精确最近邻(Exact NN)"的区别?

如果你有一个向量查询,比如:

找最相似的 Top K 向量

精确最近邻(Exact NN)

  • 必须把所有向量都算一遍距离
  • 一定是最准确结果
  • 但数据一大就爆炸(O(N))

近似最近邻(ANN)

  • 不看全部数据
  • 用"索引结构 + 近似策略"快速找候选
  • 只保证"很可能是最近的"
  • 但速度极快(通常毫秒级)

DISKANN 是什么

拆开看:

DISK + ANN

DISKANN = Disk-based Approximate Nearest Neighbor

意思是:

一种"基于磁盘存储的大规模近似最近邻搜索算法"

为什么要 DISKANN?

传统 ANN 方法(比如 HNSW / IVF)的问题:

  • ❌ 依赖内存(RAM)
  • ❌ 数据太大就放不下(亿级/十亿级向量)

DISKANN 的核心特点:

  • 📀 数据主要放在 SSD(磁盘)

  • 🧠 内存只放:

    • 图结构
    • PQ压缩码
  • 🚀 查询时:

    • 先用图快速找候选
    • 再按需读磁盘做精排

和 HNSW 的关键区别

方法 存储 规模 特点
HNSW 内存 百万~千万 快但吃内存
IVF 内存+索引 千万级 传统向量库
DISKANN 磁盘 + SSD 亿级/十亿级 为超大规模设计

SCANN

SCANN(Milvus 中的 SCANN 索引)基于 Google 的 ScaNN(Scalable Nearest Neighbors)库构建,用于解决向量相似性搜索在大规模数据下的扩展性问题,在速度与精度之间取得平衡,即使在传统算法难以处理的大型数据集上也能表现良好。

ScaNN 旨在解决向量检索中的一个核心问题:在高维空间中高效地找到最相关的向量,即使数据规模不断增长也能保持性能。

其整体架构将向量搜索拆分为三个阶段:

  1. 分区(Partitioning)

    将数据集划分为多个簇(clusters)。

    通过只在相关子集内搜索,而不是扫描全量数据,从而减少搜索空间并节省计算资源。

    ScaNN 通常使用如 k-means 等聚类算法来生成这些簇,从而提升相似性搜索效率。

  2. 量化(Quantization)

    在分区之后,ScaNN 使用一种称为**非对称向量量化(anisotropic vector quantization)**的方法进行压缩。

    传统量化方法主要最小化原始向量与压缩向量之间的整体距离,但在**最大内积搜索(MIPS)**场景中效果并不理想,因为相似性由向量内积决定,而不是欧氏距离。

    非对称量化的核心思想是:

    • 更关注保留向量之间的"方向一致性"(平行分量)
    • 即更关注对内积计算最重要的部分

    这样可以在压缩数据的同时保持更高的 MIPS 精度,使查询既快又准。

  3. 重排序(Re-ranking)

    重排序是最后一步。

    ScaNN 会对前面分区和量化阶段得到的候选结果进行精细计算,使用精确的内积距离重新排序。

    这一阶段确保最终返回的结果具有较高准确性。

    它在推荐系统、图像检索等场景中非常关键,因为:

    • 前两步负责"快速筛选"
    • 最后一步负责"精确排序"

SCANN 的性能主要由两个关键参数控制,用于在速度和精度之间进行权衡:

  1. with_raw_data

    控制是否在存储量化向量的同时保存原始向量数据。

    • 启用:提升重排序精度
    • 代价:增加存储开销
  2. reorder_k

    控制重排序阶段参与精排的候选数量。

    • 值越大:

      • ✔ 精度更高
      • ✖ 延迟更高

在 Milvus 中构建 SCANN 索引,可以使用 add_index() 方法,指定 index_typemetric_type 以及相关参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",  # 向量字段名称
    index_type="SCANN",                   # 索引类型
    index_name="vector_index",            # 索引名称
    metric_type="L2",                     # 距离度量方式
    params={
        "with_raw_data": True            # 是否保存原始向量数据
    }
)

配置说明

  • index_type:索引类型,这里为 SCANN
  • metric_type:距离计算方式(COSINE / L2 / IP)
  • params:索引构建参数
  • with_raw_data:是否同时存储原始向量数据(用于提升重排序精度)

构建索引并写入数据后,可以进行向量相似性搜索:

python 复制代码
search_params = {
    "params": {
        "reorder_k": 10,  # 重排序候选数量
        "nprobe": 8       # 搜索的簇数量
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",  # 集合名称
    anns_field="vector_field",               # 向量字段
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],         # 查询向量
    limit=10,                                 # 返回 TopK
    search_params=search_params
)

参数说明

  • reorder_k:重排序阶段参与精排的候选数量
  • nprobe:搜索时访问的簇数量

下表列出了在构建索引时可在 params 中配置的参数。

Parameter Description Value Range Tuning Suggestion
nlist 聚类单元的数量 1, 65536 较高的 nlist 可以提升剪枝效率,通常能加快粗搜索速度,但每个分区可能变小,从而降低召回率;较低的 nlist 会扫描更大的簇,提高召回率但会降低搜索速度。
with_raw_data 是否在量化表示之外同时存储原始向量数据。启用后,可以在重排序阶段使用原始向量进行更精确的相似度计算,而不是依赖量化近似值。 Type: Boolean Range: true, false Default: true 设置为 true:在对存储空间不敏感且追求更高准确率时使用,原始向量可在重排序阶段提供更精确的相似度计算。 设置为 false:用于降低存储和内存开销,尤其适合大规模数据集,但可能会带来轻微的精度下降,因为重排序阶段将使用量化向量。 推荐:在对精度要求较高的生产环境中使用 true。

下表列出了在 search_params.params 中可配置的索引搜索参数。

Parameter Description Value Range Tuning Suggestion
reorder_k 控制在重排序阶段参与精炼的候选向量数量。该参数决定初始分区与量化阶段得到的 top 候选中,有多少会被重新使用更精确的相似度计算进行评估。 Type: Integer Range: 1, int_max Default: None 较大的 reorder_k 通常可以提升搜索准确率,因为更多候选参与最终精炼。但由于额外计算,会增加查询延迟。 当召回率要求较高且可以牺牲部分速度时,建议增大 reorder_k。一个常见起始值是 TopK 的 2--5 倍。 当更关注查询速度时,可以降低该值以减少计算开销。 通常推荐范围:limit, limit × 5
nprobe 搜索的簇数量 Type: Integer Range: 1, nlist Default: 8 较大的 nprobe 会扩大搜索范围,提高召回率,但会增加查询延迟。 建议根据 nlist 成比例调整 nprobe,在速度与准确率之间取得平衡。 通常推荐范围:1, nlist

AISAQ(Milvus 2.6.4+)

AISAQ 是一种基于磁盘(disk-based)的向量索引,它在 DISKANN 的基础上进行了扩展,能够以极小的 DRAM 占用支持十亿级规模的数据集。

与 DISKANN 不同,DISKANN 会将压缩后的向量保存在内存中,而 AISAQ 采用"Near-Zero DRAM 架构",即所有数据结构都存放在 SSD 上。

AISAQ 允许在标准服务器上运行超大规模数据库,并提供多种运行模式,用于在性能与存储成本之间进行权衡。

上图对比了 DISKANN、AISAQ-Performance 和 AISAQ-Scale 的存储布局,展示了原始向量、边列表(edge list)以及 PQ 编码在 RAM 与磁盘之间的分布方式。

DISKANN vs AISAQ:

在 DISKANN 中:

  • 原始向量(raw vectors)和边列表(edge lists)存储在磁盘上
  • PQ 压缩向量存储在内存(DRAM)中

当 DISKANN 遍历到某个节点(例如向量 0)时:

  • 从磁盘加载原始向量 raw_vector_0 和边列表 edgelist_0
  • 边列表用于确定下一步要访问的邻居节点(例如 2、3、5)
  • 原始向量用于与查询向量计算精确距离并排序
  • 内存中的 PQ 数据用于近似距离计算,辅助下一步遍历

由于 PQ 数据已经缓存在 DRAM 中,因此每次访问节点只需要一次磁盘 IO,从而在较低内存消耗下实现较高查询速度。

AISAQ 提供两种运行模式,用于不同场景:

  • Performance 模式:面向需要低延迟、高吞吐的应用,例如在线语义检索。

  • Scale 模式:面向延迟要求相对宽松的应用,例如 RAG 或离线语义检索,同时支持以更低成本扩展到超大规模数据集。

AISAQ-Performance 通过将 PQ 数据从内存移动到磁盘,实现"近零 DRAM 占用",同时通过数据共置与冗余机制维持较低 IOPS。

  • 每个节点的原始向量、边列表以及其邻居的 PQ 数据都会一起存储在磁盘上
  • 这种布局保证访问一个节点(例如向量 0)仍然只需要一次磁盘 IO

由于 PQ 数据在多个节点附近存在冗余存储,索引文件体积会显著增大,从而占用更多磁盘空间。

AISAQ-Scale 专注于降低磁盘空间占用,同时满足目标应用的性能要求。

在该模式下:

  • PQ 数据在磁盘中独立存储,不做冗余复制
  • 这种设计显著减少索引大小,但会增加图遍历时的 IO 次数

为缓解 IOPS 开销,AISAQ 引入两项优化:

  • rearrange 算法:对 PQ 向量进行重排序,提高数据局部性
  • DRAM 中 PQ cache(pq_read_page_cache_size):缓存频繁访问的 PQ 数据

配置示例

yaml 复制代码
# milvus.yaml
knowhere:
  AISAQ:
    build:
      max_degree: 56  # 控制 Vamana 图中每个节点最多连接的边数
      search_list_size: 100  # 构建索引时用于邻居搜索的候选集合大小
      inline_pq: -1  # 每个索引节点内联存储的 PQ 向量数量(访问节点时读取,用于减少 IO)
      rearrange: true  # 重排 PQ 数据结构以提升局部性并减少磁盘访问(performance 模式下忽略)
      num_entry_points: 100  # 用于优化入口点选择的候选数量
      pq_code_budget_gb_ratio: 0.125  # PQ 编码占比(相对原始数据大小)
      disk_pq_code_budget_gb_ratio: 0.25  # 高精度 PQ 编码占比(用于重排序)
      pq_cache_size: 0  # DRAM 中 PQ 缓存大小(字节),加载索引时使用(performance 模式忽略)
      search_cache_budget_gb_ratio: 0  # 用于缓存热点索引节点的 DRAM 比例
    search:
      search_list: 16  # 搜索过程中维护的候选集合大小
      beamwidth: 8  # 控制并行 disk IO 数量
      vectors_beamwidth: 1  # 并行读取邻居 PQ 向量的 IO 数量(performance 模式忽略)
      pq_read_page_cache_size: 5242880 (5MiB)  # 每线程 PQ 读取缓存大小(仅 scale 模式 + rearrange=true 生效)

AISAQ 继承了 DISKANN 的部分参数:max_degreesearch_list_sizepq_code_budget_gb_ratio

这些参数会影响 AISAQ 索引的构建过程。调整它们可能影响索引大小、构建时间以及搜索质量。

Parameter Description Value Range Tuning Suggestion
max_degree 控制 Vamana 图中每个数据点最多可以拥有的连接(边)数量 Type: Integer Range: 1, 512 Default: 56 更大的值会生成更稠密的图,可能提高召回率(找到更多相关结果),但会增加内存使用和构建时间。通常建议设置在 10, 100
search_list_size 在索引构建过程中,该参数定义用于搜索每个节点最近邻的候选集合大小。每次插入节点时,算法都会维护一个当前找到的 best search_list_size 个候选集合,当该集合无法再优化时停止搜索。最终从候选集中选出 top max_degree 个作为边 Type: Integer Range: 1, 512 Default: 100 更大的值可以更大概率找到真实最近邻,从而提升图质量与召回率,但构建时间显著增加。必须 ≥ max_degree
inline_pq 每个索引节点内联存储的 PQ 向量数量(节点访问时读取,用于减少 IO) Type: Integer Range: 0, max_degree Default: -1 值越大性能越好,但占用更多磁盘空间 scale 模式:设为 0 scale 模式优化:设为 -1(自动填充未使用空间) performance 模式:设为 max_degree 0~max_degree 之间可用于平衡性能与磁盘占用
rearrange 是否重排 PQ 向量结构以提升数据局部性并减少搜索 IO(performance 模式忽略) Type: Boolean Range: true/false Default: true true:减少 IO,同时略微增加构建时间与内存开销
num_entry_points 用于优化搜索入口点选择的候选入口数量 Type: Integer Range: 0, 1000 Default: 100 值越大可能减少搜索时间(从更优入口开始)。大规模数据建议更高值(如 1000 万以上数据可设 1000)
pq_code_budget_gb_ratio PQ 编码(压缩表示)占原始数据大小的比例 Type: Float Range: (0.0, 0.25] Default: 0.125 更高比例提高准确率(保留更多信息),但增加计算复杂度。推荐范围:(0.0417, 0.25]
disk_pq_code_budget_gb_ratio 索引中用于重排序的高精度 PQ 编码占原始数据大小的比例 Type: Float Range: 0, 0.25 Default: 0.25 默认 0.25 表示压缩到原始大小的 25%(4 倍压缩),磁盘占用更低且精度影响较小 设为 0:存储全精度向量用于重排序 更大值:更高召回但更多磁盘占用
pq_cache_size DRAM 中 PQ 向量缓存大小(字节)。索引加载时加载,用于减少 IO(performance 模式忽略) Type: Integer Range: 0, 1073741824 Default: 0 更大缓存提升查询性能,但增加 DRAM 使用
search_cache_budget_gb_ratio 用于缓存高频访问索引节点的 DRAM 占比 Type: Float Range: [0.0, 0.3) Default: 0 更高值减少 IO,但增加内存占用

这些参数影响 AISAQ 的搜索过程。调整它们会影响搜索速度、延迟和资源使用。

Parameter Description Value Range Tuning Suggestion
search_list 搜索过程中维护的候选集合大小 Type: Integer Range: topk, int32_max Default: 16 值越大召回越高但延迟更高。建议设置为 ≥ top_k 或略高
beamwidth 控制搜索时并行 disk IO 数量(读取索引节点) Type: Integer Range: 1, 16 Default: 8 更高值提升并行度,但可能造成资源争用。通常推荐 2
vectors_beamwidth 控制读取邻居 PQ 向量的并行 IO 数量(performance 模式忽略) Type: Integer Range: 1, 4 且 ≤ beamwidth Default: 1 更高值提升并行度,但可能造成资源争用(每个邻居组最多含 max_degree 向量)。通常推荐 1
pq_read_page_cache_size 每个搜索线程的 DRAM PQ 读取缓存大小(字节)。用于缓存 PQ 数据页(performance 模式忽略,仅 rearrange=true 时生效) Type: Integer Range: 0, 33554432 Default: 5MiB 缓存越大性能越好,但占用更多 DRAM 建议:小规模 2MiB / 中等 5MiB / 大规模 10MiB

📊 Milvus 浮点向量索引选型表(核心对比)

索引 类型 存储方式 是否压缩 适用规模 延迟 召回 典型场景 核心特点
FLAT 暴力搜索 全内存 小(<100万) ❌最慢 ⭐⭐⭐⭐⭐ 精确搜索 / baseline 不建索引,100%精确
IVF_FLAT 倒排索引 内存 中(10万~1亿) ⭐⭐ ⭐⭐⭐⭐ 通用检索 先聚类再扫描
IVF_SQ8 IVF + 标量压缩 内存 ✔️ 中~大 ⭐⭐⭐ ⭐⭐⭐ 内存紧张 8bit量化,省内存
IVF_PQ IVF + PQ压缩 内存 ✔️✔️ 大(百万~十亿) ⭐⭐⭐⭐ ⭐⭐⭐ 超大规模检索 强压缩,牺牲精度
IVF_RABITQ IVF + 高级量化 内存 ✔️✔️ ⭐⭐⭐⭐ ⭐⭐⭐⭐ 高精度压缩检索 比PQ更精细
HNSW 图索引 内存 中(百万级) ⭐⭐⭐⭐⭐(快) ⭐⭐⭐⭐⭐ 高性能语义搜索 图遍历,延迟低
HNSW_SQ HNSW + SQ 内存 ✔️ ⭐⭐⭐⭐ ⭐⭐⭐⭐ 内存优化版HNSW 省内存
HNSW_PQ HNSW + PQ 内存 ✔️✔️ 中~大 ⭐⭐⭐⭐ ⭐⭐⭐⭐ 大规模高效检索 图+强压缩
HNSW_PRQ HNSW + PQ+RQ 内存 ✔️✔️✔️ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 高精度压缩检索 比PQ更准
DISKANN 图 + 磁盘 SSD + 少量内存 ✔️ 超大(亿~十亿) ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 超大规模在线检索 SSD时代主力
SCANN partition + quant + rerank 内存 ✔️ ⭐⭐⭐⭐ ⭐⭐⭐⭐ 推荐系统 / MIPS Google ScaNN
AISAQ DISKANN升级版 全SSD(极少DRAM) ✔️✔️ 超大(10亿+) ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 超大规模低成本 "几乎零内存"

🧠 一句话理解每一类

1️⃣ FLAT

👉 "不玩技巧,直接全扫"

2️⃣ IVF 系列

👉 "先分桶,再桶内搜"

  • IVF_FLAT:不压缩(准但慢)
  • IVF_PQ:强压缩(省内存但掉精度)
  • IVF_SQ8:轻压缩(折中)

3️⃣ HNSW 系列

👉 "图结构导航(像地图找路)"

  • HNSW:最快最准(但吃内存)
  • HNSW_PQ:图 + 压缩(适合大数据)
  • HNSW_PRQ:PQ + RQ(更准)

4️⃣ DISKANN

👉 "内存放不下 → 上 SSD"

  • 核心:磁盘图 + PQ缓存
  • 特点:适合亿级数据
  • 本质:用 IO 换内存

5️⃣ SCANN(Google系)

👉 "分区 + 量化 + rerank"

  • 推荐系统常用
  • 更偏 MIPS(内积检索)

6️⃣ AISAQ

👉 "DISKANN进化版:几乎不用内存"

  • PQ也可以下沉到 SSD
  • DRAM接近 0
  • 适合:极大规模、成本敏感场景

🧩 选型快速指南(最重要)

✔️ 小数据 / 高精度

👉 FLAT / HNSW

✔️ 通用生产(推荐默认)

👉 HNSW 或 IVF_FLAT

✔️ 内存有限 / 数据较大

👉 IVF_PQ / HNSW_PQ

✔️ 亿级数据

👉 DISKANN

✔️ 超大规模 + 成本敏感

👉 AISAQ

✔️ 推荐系统 / MIPS

👉 SCANN

二进制向量索引

BIN_IVF_FLAT

BIN_IVF_FLAT 索引是 IVF_FLAT 索引的一个变体,专门用于二进制嵌入(binary embeddings)

它通过将向量数据首先划分为多个聚类(nlist 个簇),然后将目标查询向量与各个簇中心进行比较,从而提升查询效率。

BIN_IVF_FLAT 可以显著减少查询时间,同时允许用户在精度与速度之间进行调优

要在 Milvus 的向量字段上构建 BIN_IVF_FLAT 索引,可以使用 add_index() 方法,并指定 index_typemetric_type 以及其他参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_binary_vector_field_name",  # 向量字段名称
    index_type="BIN_IVF_FLAT",                  # 索引类型
    index_name="vector_index",                  # 索引名称
    metric_type="HAMMING",                      # 相似度度量方式
    params={
        "nlist": 64,  # 索引的聚类数量
    }
)

配置说明:

  • index_type :要构建的索引类型。本例中为 BIN_IVF_FLAT

  • metric_type :用于计算向量距离的方法

    二进制向量支持:

    • HAMMING(默认)
    • JACCARD
      详情请参考 Metric Types
  • params:索引构建的附加参数

    • nlist:将数据划分的聚类数量

索引构建完成并写入数据后,可以进行相似度搜索:

python 复制代码
search_params = {
    "params": {
        "nprobe": 10,  # 搜索的簇数量
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",  # 集合名称
    anns_field="binary_vector_field",        # 二进制向量字段
    data=[query_binary_vector],              # 查询向量
    limit=3,                                 # 返回 TopK
    search_params=search_params
)

配置说明:

  • params:搜索时的附加参数

    • nprobe:需要搜索的聚类数量

下表列出了在构建索引时 params 中可以配置的参数。

参数 描述 取值范围 调优建议
nlist 在索引构建过程中,使用 k-means 算法生成的聚类数量。每个聚类由一个中心点(centroid)表示,并维护一个向量列表。增大该参数会减少每个聚类中的向量数量,从而形成更小、更聚焦的分区。 Integer,范围:1, 65536 默认值:128 更大的 nlist 可以生成更精细的聚类,从而提升召回率,但会增加索引构建时间。需要根据数据规模和资源情况进行优化。一般建议范围:32, 4096

下表列出了在搜索索引时 search_params.params 中可以配置的参数。

参数 描述 取值范围 调优建议
nprobe 用于候选搜索的聚类数量。该值越大,搜索的簇越多,可以扩大搜索范围,从而提升召回率,但会增加查询延迟。 Integer,范围:1, nlist 默认值:8 增大该值可以提升召回率,但可能降低搜索速度。建议根据 nlist 按比例设置 nprobe,以在速度与精度之间取得平衡。一般推荐范围:1, nlist

BIN_FLAT

BIN_FLAT 索引是 FLAT 索引的一种变体,专门用于二进制嵌入(binary embeddings)

它适用于对向量相似度搜索要求**绝对精度(100% accuracy)**的场景,尤其是在相对较小的、百万级数据集上表现良好。

BIN_FLAT 采用**穷举搜索(exhaustive search)**的方法------即将查询向量与数据集中所有向量逐一进行比较,从而保证结果的绝对精确性。

这种精确性使其成为评估其他索引性能的理想基准(因为其他索引通常无法达到 100% recall)。

但由于其"全量扫描"的特性,它在大规模数据上是最慢的索引方式

要在 Milvus 的二进制向量字段上构建 BIN_FLAT 索引,可以使用 add_index() 方法,并指定 index_typemetric_type 参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_binary_vector_field_name",  # 向量字段名称
    index_type="BIN_FLAT",                      # 索引类型
    index_name="vector_index",                  # 索引名称
    metric_type="HAMMING",                      # 相似度度量方式
    params={}                                   # BIN_FLAT 无额外参数
)

配置说明:

  • index_type :索引类型,本例为 BIN_FLAT

  • metric_type :向量距离计算方式

    二进制嵌入支持:

    • HAMMING(默认)
    • JACCARD
      详情参考 Metric Types
  • params:BIN_FLAT 不需要任何额外参数

索引构建完成并写入数据后,可以进行相似度搜索:

python 复制代码
res = MilvusClient.search(
    collection_name="your_collection_name",   # 集合名称
    anns_field="binary_vector_field",         # 二进制向量字段
    data=[query_binary_vector],               # 查询向量
    limit=3,                                  # 返回 TopK
    search_params={"params": {}}              # BIN_FLAT 无额外参数
)

对于 BIN_FLAT 索引,无论在索引构建阶段还是搜索阶段,都不需要任何额外参数

MINHASH_LSH

在大规模机器学习数据集中,高效去重与相似性搜索至关重要,尤其是在清洗用于大语言模型(LLMs)的训练语料时。

当数据规模达到百万甚至十亿级别时,传统的精确匹配方法会变得非常缓慢且成本高昂。

Milvus 中的 MINHASH_LSH 索引通过结合两种强大的技术,实现了快速、可扩展且高精度的近似去重能力:

  • MinHash:快速生成紧凑的"签名(fingerprint)",用于估计文档相似度
  • 局部敏感哈希(LSH):基于 MinHash 签名快速找到相似文档分组

Jaccard 相似度用于衡量两个集合 A 和 B 的重叠程度,定义为:

其取值范围为:

  • 0:完全不相交
  • 1:完全相同

但在大规模数据集中,精确计算所有文档对的 Jaccard 相似度是非常昂贵的:

  • 时间复杂度:O(n²)
  • 当 n 很大时不可扩展

因此,它不适用于 LLM 训练语料清洗或 Web 级文档分析。

MinHash 是一种概率方法,用于高效估计 Jaccard 相似度。

它的核心思想是:

将集合转换为一个紧凑的签名向量(signature),保留足够的信息用于近似相似度计算。

核心性质:

两个集合越相似,它们的 MinHash 签名在相同位置上匹配的概率越高。

因此可以用签名匹配程度来近似 Jaccard 相似度,而无需直接比较完整集合。

MinHash 计算流程:

  1. Shingling(分片)

    将文档拆分为重叠的 token 序列(shingles)

  2. Hashing(哈希)

    对每个 shingle 应用多个独立哈希函数

  3. Min 选择(取最小值)

    对每个哈希函数,取所有 shingles 中的最小哈希值

整个流程如下图所示:

MinHash 签名的维度由哈希函数数量决定:

  • 维度越高 → 近似精度越高

  • 但:

    • 存储开销增加
    • 计算成本增加

虽然 MinHash 已经降低了计算成本,但如果仍然对所有签名进行两两比较,仍然非常低效。

因此引入 LSH(局部敏感哈希)

LSH 可以让相似项以高概率落入同一个"桶(bucket)",从而避免全量比较。

处理流程:

  1. 签名分段(Signature segmentation)

    将一个 n 维的 MinHash 签名划分为 b 个 band(分段)。每个 band 包含 r 个连续的哈希值,因此满足总长度关系:n=b×r

    例如,如果你有一个 128 维的 MinHash 签名(n = 128),并将其划分为 32 个 band(b = 32),那么每个 band 就包含 4 个哈希值(r = 4)。

  2. Band 级哈希

    分段之后,每个 band 会单独使用标准哈希函数进行处理,并被映射到一个桶(bucket)中。

    如果两个签名在某个 band 中产生相同的哈希值------也就是说它们落入同一个 bucket------那么它们就被认为是潜在的相似候选。

  3. 候选选择(Candidate selection)

    只要两个文档在任意一个 band 中发生碰撞,就会被选为候选相似对。

为什么 LSH 有效?

从数学上讲,如果两个签名的 Jaccard 相似度为 s:

  • 在某一个位置(hash 位置)相同的概率是:s
  • 在一个 band(r 行)中全部相同的概率是:s^r
  • 在至少一个 band 中匹配的概率是:

    考虑三个文档,它们都具有 128 维的 MinHash 签名:

首先,LSH 将这 128 维的签名划分为 32 个 band(分组),每个 band 包含 4 个连续的值:

然后,每个 band 会通过一个哈希函数分别映射到不同的桶(bucket)中。如果两个文档在某个 band 中的哈希结果相同------也就是落入同一个桶------那么它们就会被认为是潜在的相似候选对。

在下面的示例中,由于 Document A 和 Document B 在 Band 0 中发生了哈希碰撞,因此它们被选为相似候选:

band 的数量由 mh_lsh_band 参数控制。

MHJACCARD:用于比较 MinHash 签名

MinHash 签名使用固定长度的二进制向量来近似集合之间的 Jaccard 相似度。但由于这些签名并不保留原始集合的信息,因此无法直接使用标准的距离度量(如 JACCARD、L2 或 COSINE)来进行比较。

为了解决这个问题,Milvus 引入了一种专门的度量类型 MHJACCARD,用于专门比较 MinHash 签名。

在 Milvus 中使用 MinHash 时需要满足以下条件:

  • 向量字段类型必须是 BINARY_VECTOR
  • index_type 必须是 MINHASH_LSH(或 BIN_FLAT
  • metric_type 必须设置为 MHJACCARD

使用其他 metric 要么是无效的,要么会得到错误结果。

由 MinHash LSH 支持的去重流程,使 Milvus 能够在写入 collection 之前,高效识别并过滤近似重复的文本或结构化数据。

去重流程如下:

  • 分块与预处理(Chunk & preprocess)

    将输入的文本或结构化数据(如记录、字段)拆分为多个块;对文本进行标准化处理(如小写化、去标点),并根据需要去除停用词。

  • 特征构建(Feature construction)

    构建用于 MinHash 的 token 集合(例如文本的 shingle,或结构化数据中拼接后的字段 token)。

  • 生成 MinHash 签名(MinHash signature generation)

    为每个 chunk 或记录计算 MinHash 签名。

  • 二进制向量转换(Binary vector conversion)

    将签名转换为 Milvus 可用的二进制向量格式。

  • 写入前检索(Search before insert)

    使用 MinHash LSH 索引,在目标集合中检索与当前数据的近似重复项。

  • 插入与存储(Insert & store)

    只插入唯一数据到 collection 中,这些数据之后也可用于后续去重检测。

在 Milvus 中使用 MinHash LSH 之前,你必须先生成 MinHash 签名。这些紧凑的二进制签名用于近似集合之间的 Jaccard 相似度,并且是 Milvus 中基于 MHJACCARD 搜索所必需的。

根据你的工作负载,可以选择:

  • 使用 Python 的 datasketch(推荐用于原型开发,简单易用)
  • 使用分布式工具(例如 Spark、Ray)处理大规模数据集
  • 如果性能优化很关键,可以实现自定义逻辑(NumPy、C++ 等)

在本指南中,我们使用 datasketch,以保证简单性以及与 Milvus 输入格式的兼容性。

我们将生成 256 维 MinHash 签名,每个 hash 值用 64 位整数表示。这与 MINHASH_LSH 所期望的向量格式一致。

python 复制代码
from datasketch import MinHash
import numpy as np

MINHASH_DIM = 256
HASH_BIT_WIDTH = 64

def generate_minhash_signature(text, num_perm=MINHASH_DIM) -> bytes:
    m = MinHash(num_perm=num_perm)
    for token in text.lower().split():
        m.update(token.encode("utf8"))
    return m.hashvalues.astype('>u8').tobytes()  # 返回 2048 字节

每个签名是:

256 × 64 bit = 2048 字节

该字节串可以直接插入 BINARY_VECTOR 字段。关于 Milvus 中二进制向量的更多信息,请参考 Binary Vector。

(可选)准备原始 token 集(用于更精细的搜索)

默认情况下,Milvus 只使用 MinHash 签名和 LSH 索引来查找近似邻居。这种方式很快,但可能会产生误报或遗漏相似项。

如果你希望获得更精确的 Jaccard 相似度,Milvus 支持"精细检索(refined search)",它会使用原始 token 集:

要启用它:

  • 将 token 集作为单独的 VARCHAR 字段存储
  • 在构建索引参数时设置 "with_raw_data": True
  • 在执行相似度搜索时启用 "mh_search_with_jaccard": True

token 集提取示例

python 复制代码
def extract_token_set(text: str) -> str:
    tokens = set(text.lower().split())
    return " ".join(tokens)

当你的 MinHash 向量和原始 token 集准备好之后,就可以使用 Milvus 的 MINHASH_LSH 来存储、构建索引并进行搜索。

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(uri="http://localhost:19530")  # 如果你的地址不同请修改

定义 Collection Schema(集合结构)

需要定义如下字段:

  • 主键(primary key)
  • 用于 MinHash 签名的 BINARY_VECTOR 字段
  • 用于原始 token 集的 VARCHAR 字段(如果启用精细检索)
  • 可选:存储原始文本的 document 字段
python 复制代码
from pymilvus import DataType

VECTOR_DIM = MINHASH_DIM * HASH_BIT_WIDTH  # 256 × 64 = 8192 bits

schema = client.create_schema(auto_id=False, enable_dynamic_field=False)
schema.add_field("doc_id", DataType.INT64, is_primary=True)
schema.add_field("minhash_signature", DataType.BINARY_VECTOR, dim=VECTOR_DIM)
schema.add_field("token_set", DataType.VARCHAR, max_length=1000)  # 精细检索必需
schema.add_field("document", DataType.VARCHAR, max_length=1000)

构建启用 Jaccard 精细化的 MINHASH_LSH 索引:

python 复制代码
index_params = client.prepare_index_params()
index_params.add_index(
    field_name="minhash_signature",
    index_type="MINHASH_LSH",
    metric_type="MHJACCARD",
    params={
        "mh_element_bit_width": HASH_BIT_WIDTH,  # 必须与签名位宽一致
        "mh_lsh_band": 16,                       # band 数(128/16 = 每 band 8 个 hash)
        "with_raw_data": True                    # Jaccard 精细化必需
    }
)

client.create_collection("minhash_demo", schema=schema, index_params=index_params)

对每个文档,需要准备:

  • MinHash 二进制签名
  • 序列化 token 集字符串
  • (可选)原始文本
python 复制代码
documents = [
    "machine learning algorithms process data automatically",
    "deep learning uses neural networks to model patterns"
]

insert_data = []
for i, doc in enumerate(documents):
    sig = generate_minhash_signature(doc)
    token_str = extract_token_set(doc)
    insert_data.append({
        "doc_id": i,
        "minhash_signature": sig,
        "token_set": token_str,
        "document": doc
    })

client.insert("minhash_demo", insert_data)
client.flush("minhash_demo")

Milvus 支持两种 MinHash LSH 相似度搜索模式:

  1. 准备查询

    对查询文本生成 MinHash 签名(必须与插入时维度和编码一致):

    python 复制代码
    query_text = "neural networks model patterns in data"
    query_sig = generate_minhash_signature(query_text)
  2. 近似搜索(仅 LSH)

    速度快但结果是概率性的,可能有误差:

    python 复制代码
    search_params = {
        "metric_type": "MHJACCARD",
        "params": {}
    }
    
    approx_results = client.search(
        collection_name="minhash_demo",
        data=[query_sig],
        anns_field="minhash_signature",
        search_params=search_params,
        limit=3,
        output_fields=["doc_id", "document"],
        consistency_level="Strong"
    )
    
    for i, hit in enumerate(approx_results[0]):
        sim = 1 - hit['distance']
        print(f"{i+1}. Similarity: {sim:.3f} | {hit['entity']['document']}")
  3. 精细搜索(推荐)

    使用原始 token 集重新计算 Jaccard,相似度更准确,但稍慢:

    python 复制代码
    search_params = {
        "metric_type": "MHJACCARD",
        "params": {
            "mh_search_with_jaccard": True,  # 启用真实 Jaccard 计算
            "refine_k": 5                    # 对 top 5 候选进行精细计算
        }
    }
    
    refined_results = client.search(
        collection_name="minhash_demo",
        data=[query_sig],
        anns_field="minhash_signature",
        search_params=search_params,
        limit=3,
        output_fields=["doc_id", "document"],
        consistency_level="Strong"
    )
    
    for i, hit in enumerate(refined_results[0]):
        sim = 1 - hit['distance']
        print(f"{i+1}. Similarity: {sim:.3f} | {hit['entity']['document']}")

下表列出了在构建索引时 params 中可以配置的参数。

参数 说明 取值范围 调优建议
mh_element_bit_width MinHash 签名中每个 hash 值的位宽,必须是 8 的倍数 8, 16, 32, 64 建议使用 32,在性能和精度之间较平衡;64 精度更高但数据集更大;16 可节省内存但会降低一定精度
mh_lsh_band 将 MinHash 签名划分为多少个 band,用于 LSH,控制召回率与性能的权衡 1, signature_length 对 128 维签名:建议从 32 个 band(每 band 4 个值)开始;提高召回率可调到 64;提升性能可降到 16;必须能整除签名长度
mh_lsh_code_in_mem 是否将 LSH hash code 存储在匿名内存中(true)或使用内存映射(false) true, false 大数据集(>100万集合)建议 false 以降低内存;小数据集可用 true 提升搜索速度
with_raw_data 是否同时存储原始 MinHash 签名,用于精细化检索 true, false 需要高精度时用 true(允许更高存储成本);需要节省存储时用 false(略微降低精度)
mh_lsh_bloom_false_positive_prob LSH bucket 优化中 Bloom filter 的误报概率 0.001 ~ 0.1 建议 0.01(均衡内存与精度);0.001 更准但占内存更高;0.05 更省内存但精度下降

下表列出了在 search_params.params 中搜索时可配置的参数。

参数 说明 取值范围 调优建议
mh_search_with_jaccard 是否对候选结果进行精确 Jaccard 相似度计算(用于精细化) true, false 高精度场景(如去重)建议 true;可接受误差时用 false 提升速度
refine_k 在 Jaccard 精细计算前召回的候选数量(仅当 mh_search_with_jaccard=true 时生效) top_k, top_k × 10 建议设置为 top_k 的 2~5 倍,在召回率与性能间平衡;越大召回越高但计算更慢
mh_lsh_batch_search 是否启用批量优化以支持多查询并行搜索 true, false 多查询场景建议 true 提升吞吐;单查询建议 false 降低内存开销

稀疏向量索引(Sparse Vector Indexes)

SPARSE_INVERTED_INDEX

SPARSE_INVERTED_INDEX 是 Milvus 中用于高效存储和检索稀疏向量的一种索引类型。

该索引基于倒排索引(Inverted Index)原理构建,为稀疏数据提供高效的搜索结构。

在 Milvus 中为稀疏向量字段构建 SPARSE_INVERTED_INDEX 索引时,可以使用 add_index() 方法,并指定 index_typemetric_type 以及其他参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_sparse_vector_field_name",  # 稀疏向量字段名称
    index_type="SPARSE_INVERTED_INDEX",          # 索引类型
    index_name="sparse_inverted_index",          # 索引名称
    metric_type="IP",                            # 相似度度量方式
    params={"inverted_index_algo": "DAAT_MAXSCORE"},  # 索引构建与查询算法
)

参数说明

index_type

要构建的索引类型,本例中为:

  • SPARSE_INVERTED_INDEX

metric_type(相似度度量)

用于计算稀疏向量相似度的方法,支持:

  • IP(Inner Product,内积)

    • 使用点积计算相似度
  • BM25

    • 通常用于全文检索,更偏向文本语义匹配

params.inverted_index_algo(倒排索引算法)用于构建和查询索引的算法,支持以下几种:

  1. DAAT_MAXSCORE(默认)

    优化的 Document-at-a-Time(DAAT)查询处理算法。

    • 使用 MaxScore 策略
    • 适合较大的 k 值或包含很多查询词的情况
    • 通过跳过低贡献词和文档来提升性能
    • 将词分为"关键词"和"非关键词",优先处理可能影响 TopK 的词
  2. DAAT_WAND

    基于 WAND 算法的 DAAT 查询优化。

    • 利用最大影响分数跳过不可能进入 TopK 的文档
    • 每次命中计算成本较高
    • 更适合小 k 值或短查询(更容易跳过无关项)
  3. TAAT_NAIVE

    基础的 Term-at-a-Time(TAAT)查询方式。

    • 比 DAAT_MAXSCORE 和 DAAT_WAND 更慢

    • 优势在于:

      • 不依赖缓存的最大影响分数
      • 可以动态适应全局参数变化(如 avgdl)
    • 与 DAAT 算法相比更"朴素",但更灵活

索引构建并插入数据后,可以在其上执行相似度搜索。

python 复制代码
# 准备查询稀疏向量
query_vector = [{1: 0.2, 50: 0.4, 1000: 0.7}]

res = MilvusClient.search(
    collection_name="your_collection_name",  # 集合名称
    anns_field="vector_field",                # 向量字段名称
    data=query_vector,                         # 查询向量
    limit=3                                    # 返回 TopK 结果
)

下表列出了在构建索引时 params 中可配置的参数。

参数 说明 取值范围 调优建议
inverted_index_algo 用于构建和查询索引的算法,决定索引如何处理查询 "DAAT_MAXSCORE"(默认),"DAAT_WAND","TAAT_NAIVE" 当 k 值较大或查询词较多时,建议使用 "DAAT_MAXSCORE",可通过跳过低贡献文档提升性能;对于较小 k 或短查询,推荐 "DAAT_WAND",利用更高效的剪枝;如需根据集合变化(如 avgdl)动态调整,可使用 "TAAT_NAIVE"

下表列出了在 search_params.params 中搜索时可配置的参数。

参数 说明 取值范围 调优建议
drop_ratio_search 搜索过程中忽略最小值的比例,用于减少噪声 0.0 ~ 1.0(例如 0.2 表示忽略最小 20% 的值) 可根据查询向量的稀疏程度和噪声水平进行调优

该参数用于控制在搜索过程中丢弃低幅度(low-magnitude)值的比例。

  • 增大该值(例如设置为 0.2):

    • 可以减少噪声
    • 使搜索更聚焦于更重要的特征
    • 可能提升精度与效率
  • 但代价是:

    • 可能降低召回率
    • 因为部分潜在相关信号被过滤掉

因此,需要在召回率(recall)与准确性(accuracy)之间进行权衡,根据实际业务场景选择合适的值。

标量索引(Scalar Indexes)

BITMAP

Bitmap(位图索引)是一种高效的索引技术,用于提升低基数(low-cardinality)标量字段的查询性能。

基数(Cardinality) 指的是字段中不同值的数量。如果一个字段的不同取值较少,则称其为低基数字段。

该索引类型通过将字段值转换为紧凑的二进制格式,并对其执行高效的位运算,从而减少标量查询的检索时间。

与其他索引类型相比,在处理低基数字段时,位图索引通常具有更高的空间效率和更快的查询速度。

Bitmap(位图)由两个词组成:Bit(位)和 Map(映射)。

  • Bit(位):计算机中最小的数据单位,只能取 0 或 1
  • Map(映射):指根据规则将数据映射为 0 或 1 的过程

位图索引由两个主要部分组成:

  • bitmaps(位图)
  • keys(键)

其中:

  • keys 表示索引字段中的唯一值
  • 每一个唯一值对应一个 bitmap
  • bitmap 的长度等于集合中的记录数
  • bitmap 中的每一位对应一条记录

规则如下:

  • 如果某条记录的字段值等于该 key,则对应 bit 设为 1
  • 否则设为 0

考虑一个包含 CategoryPublic 字段的文档集合。

现在我们想查询:

  • Category 为 Tech
  • 并且对 Public 开放的文档

在这个例子中:

  • bitmap 索引的 keys 为:

    • Tech
    • Public

对于字段Category(Tech):

复制代码
Tech: [1, 0, 1, 0, 0]

表示:

  • 第 1 条和第 3 条记录属于 Tech 类别

    Public: [1, 0, 0, 1, 0]

表示:

  • 第 1 条和第 4 条记录是公开的

为了找到同时满足两个条件的记录,需要对两个 bitmap 进行按位与(AND)操作:

复制代码
Tech AND Public: [1, 0, 0, 0, 0]

结果 bitmap:

复制代码
[1, 0, 0, 0, 0]

表示:

  • 只有第 1 条记录同时满足:

    • 属于 Tech
    • 且是 Public

在 Milvus 中创建 bitmap 索引,可以使用 create_index() 方法,并将 index_type 参数设置为 "BITMAP"

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
)

index_params = client.create_index_params()  # 创建一个空的 IndexParams 对象,无需提前指定索引参数
index_params.add_index(
    field_name="category",                 # 需要建立索引的标量字段名称
    index_type="BITMAP",                  # 索引类型
    index_name="category_bitmap_index"    # 索引名称
)

client.create_index(
    collection_name="my_collection",      # 指定集合名称
    index_params=index_params
)

在这个示例中,我们在 my_collection 集合的 category 字段上创建了 bitmap 索引。

add_index() 方法用于指定:

  • 字段名(field name)
  • 索引类型(index type)
  • 索引名称(index name)

一旦 bitmap 索引创建完成,就可以在查询操作中使用 filter 参数进行标量过滤。

这可以利用 bitmap 索引高效地缩小搜索范围。

删除索引(Drop an index)

可以使用 drop_index() 方法从集合中移除已有索引。

注意版本差异:

  • v2.6.3 及更早版本

    • 删除标量索引前必须先释放(release)集合
  • v2.6.4 及以后版本

    • 不再需要释放集合
    • 只要索引不再使用,可以直接删除
python 复制代码
client.drop_index(
    collection_name="my_collection",       # 集合名称
    index_name="category_bitmap_index"     # 要删除的索引名称
)

限制(Limits):

  1. Bitmap 索引仅支持非主键的标量字段

  2. 字段类型必须属于以下之一:

    • BOOL

    • INT8 / INT16 / INT32 / INT64

    • VARCHAR

    • ARRAY(数组元素必须是以下类型之一):

      • BOOL
      • INT8 / INT16 / INT32 / INT64
      • VARCHAR
  3. 不支持的数据类型:

    • FLOAT / DOUBLE

      • 浮点数类型不适用于 bitmap 的二进制结构
    • JSON

      • JSON 结构复杂,无法高效映射为 bitmap
  4. 使用限制说明

    Bitmap 索引不适用于高基数(high-cardinality)字段,即取值种类非常多的字段。

    一般经验:

    • 当字段的基数 < 500 时,bitmap 索引效果最好

    • 当基数超过该阈值时:

      • 性能提升会下降
      • 存储开销会显著增加

    替代方案:

    对于高基数字段,建议根据具体场景选择其他索引类型,例如:

    • Inverted Index(倒排索引)
    • 或其他适合查询模式的索引结构

INVERTED(倒排索引)

当你需要对数据执行高频过滤查询时,INVERTED(倒排索引)可以显著提升查询性能。

Milvus 不再逐条扫描所有文档,而是通过倒排索引快速定位满足过滤条件的记录。

在以下场景中使用倒排索引:

  1. 按特定值过滤

    查找某个字段等于指定值的记录,例如:

    • category == "electronics"
  2. 过滤文本内容

    对 VARCHAR 字段进行高效检索

  3. 查询 JSON 字段

    在 JSON 结构中按 key 进行过滤查询

性能优势:在大规模数据集上,INVERTED 索引可以将查询时间从秒级降低到毫秒级,因为它避免了全表扫描。

Milvus 中的倒排索引将:

每一个唯一字段值(term)映射到包含该值的文档 ID 集合

这种结构非常适合:

  • 重复值字段
  • 分类字段(categorical fields)

工作流程(两步):

  1. 正向映射(Forward mapping)

    • 文档 ID → 字段值
    • 每条记录指向它对应的字段值
  2. 倒排映射(Inverted mapping)

    • 字段值(Term)→ 文档 ID 列表
    • Milvus 收集所有唯一值,并建立"值 → ID集合"的映射

例如:

  • "electronics"1, 3
  • "books"2, 5

当你按某个具体值进行过滤时(例如 category == "electronics"),Milvus 会直接在索引中查找该字段值(term),并返回所有匹配的 ID。

这种方式避免了全量数据扫描,从而实现快速过滤,尤其适用于分类字段重复值较多的字段

INVERTED 索引支持所有标量字段类型,例如:

  • BOOL
  • INT8 / INT16 / INT32 / INT64
  • FLOAT / DOUBLE
  • VARCHAR
  • JSON
  • ARRAY

不过需要注意的是:对 JSON 字段进行索引时,其索引参数与普通标量字段相比会略有不同。

要在非 JSON 字段上创建索引,请按照以下步骤操作:

  1. 准备索引参数

    python 复制代码
    from pymilvus import MilvusClient
    
    client = MilvusClient(uri="http://localhost:19530")  # 替换为你的服务地址
    
    # 创建空的索引参数对象
    index_params = client.prepare_index_params()
  2. 添加 INVERTED 索引

    python 复制代码
    index_params.add_index(
        field_name="category",       # 需要建立索引的字段名称
        index_type="INVERTED",       # 指定索引类型为 INVERTED
        index_name="category_index"  # 索引名称
    )
  3. 创建索引

    python 复制代码
    client.create_index(
        collection_name="my_collection",  # 替换为你的集合名称
        index_params=index_params
    )

你也可以在 JSON 字段的**指定路径(path)**上创建 INVERTED 索引。

但需要额外参数来指定 JSON 路径和数据类型:

python 复制代码
index_params.add_index(
    field_name="metadata",                    # JSON 字段名称
    index_type="INVERTED",
    index_name="metadata_category_index",
    params={
        "json_path": "metadata[\"category\"]",   # JSON key 路径
        "json_cast_type": "varchar"             # 索引时转换的数据类型
    }
)

创建索引

python 复制代码
client.create_index(
    collection_name="my_collection",  # 替换为你的集合名称
    index_params=index_params
)

可以使用 drop_index() 方法删除已有索引。

注意版本差异:

  • v2.6.3 及更早版本

    • 删除标量索引前必须先 release collection
  • v2.6.4 及以后版本

    • 可以直接删除索引
    • 不再需要先释放集合
python 复制代码
client.drop_index(
    collection_name="my_collection",   # 集合名称
    index_name="category_index"        # 要删除的索引名称
)

最佳实践(Best practices)

  • 在数据加载后再建索引

    • 对已存在数据的集合建索引性能更好
  • 使用清晰的索引名称

    • 名称应能体现字段和用途
  • 监控索引性能

    • 创建索引前后对比查询性能变化
  • 根据查询模式设计索引

    • 在经常过滤的字段上建立索引

NGRAM(N 元索引)

Milvus 中的 NGRAM 索引用于加速对 VARCHAR 字段或 JSON 字段中特定路径的 LIKE 查询。

在构建索引之前,Milvus 会将文本拆分为固定长度 n 的短重叠子串(n-grams)

例如,当 n = 3 时,单词 "Milvus" 会被拆分为:

  • "Mil"
  • "ilv"
  • "lvu"
  • "vus"

这些 n-grams 会被存储在倒排索引中,每个 gram 映射到包含它的文档 ID。

在查询时,该索引可以快速缩小候选范围,从而显著提升查询性能。

当你需要以下类型的快速过滤时,可以使用 NGRAM 索引:

  • 前缀匹配:name LIKE "data%"
  • 后缀匹配:title LIKE "%vector"
  • 中缀匹配:path LIKE "%json%"
  • 模糊/通配符查询

Milvus 中 NGRAM 索引分为两个阶段:

  1. 构建索引(Build index)

    将文本拆分为 n-grams,并构建倒排索引(ingest 阶段完成)

  2. 加速查询(Accelerate queries)

    利用索引缩小候选集,然后再进行精确匹配验证

阶段 1:构建索引(Build index)

在数据写入时,Milvus 通过以下两个步骤构建 NGRAM 索引:

  1. 文本拆分为 n-grams

    Milvus 使用一个长度为 n 的滑动窗口,在字符串上滑动并提取子串(n-grams)。

    生成范围由:

    • min_gram
    • max_gram

    共同控制。

    参数说明

    • min_gram

      • 最短 n-gram 长度
      • 同时定义了查询中最小可索引子串长度
    • max_gram

      • 最大 n-gram 长度
      • 查询时用于长字符串的最大滑动窗口

    当:

    • min_gram = 2
    • max_gram = 3

    字符串 "AI database" 会被拆分为:

    2-grams:

    • AI
    • I_
    • _d
    • da
    • at
    • ...

    3-grams:

    • AI_
    • I_d
    • _da
    • dat
    • ata
    • ...

    注意事项

    • 对于区间 [min_gram, max_gram],Milvus 会生成所有长度范围内的 n-grams

    例如 [2,4] + "text"

    • 2-grams:te, ex, xt
    • 3-grams:tex, ext
    • 4-grams:text

    语言无关性

    • N-gram 基于字符(character-based),与语言无关

    例如中文:

    复制代码
    "向量数据库" + min_gram = 2

    会被拆为:

    • 向量
    • 量数
    • 数据
    • 据库

    其他规则

    • 空格与标点也作为字符参与拆分

    • 保留原始大小写

      • "Database""database" 会生成不同 n-grams(大小写敏感)
  2. 构建倒排索引

    将每个 n-gram 映射到包含它的文档 ID 列表。

    例如:

    复制代码
    "AI" → [1, 5, 6, 8, 9]

    表示:

    • 这些文档都包含 "AI" 这个 gram

    注意事项

    • [min_gram, max_gram] 范围越大:

      • 生成的 gram 越多
      • posting list(倒排列表)越大
      • 内存占用增加

    如果内存紧张,可以考虑 mmap 模式优化。

阶段 2:加速查询(Accelerate queries)

当执行 LIKE 查询时,Milvus 使用 NGRAM 索引进行如下优化:

  1. 提取查询词

    从 LIKE 表达式中提取不含通配符的连续子串:

    例如:

    复制代码
    "%database%" → "database"
  2. 查询词拆分

    根据长度 L 与 min_gram / max_gram 决定处理方式:

    情况 1:L < min_gram

    • 无法使用索引
    • 退化为全表扫描

    情况 2:min_gram ≤ L ≤ max_gram

    • 整个查询词作为一个 gram
    • 不再拆分

    情况 3:L > max_gram

    • 使用 max_gram 作为窗口大小
    • 将查询词拆分为多个 overlapping grams

    例如:

    复制代码
    max_gram = 3
    query = "database" (长度 8)

    拆分为:

    • dat
    • ata
    • tab
    • ...
  3. 查找 gram 并求交集

    Milvus:

    1. 在倒排索引中查找每个 gram
    2. 获取对应的文档 ID 列表
    3. 对所有列表做交集运算

    得到候选文档集合

  4. 验证并返回结果

    对候选集合执行最终的 LIKE 精确匹配:

    • 只检查候选文档
    • 避免全表扫描
    • 返回最终结果

NGRAM 索引的核心思想是:

用"字符切片 + 倒排索引 + 候选集过滤"来加速 LIKE 查询

它特别适用于:

  • 模糊匹配
  • 前缀/后缀/中缀查询
  • 文本字段过滤

你可以在 VARCHAR 字段上创建 NGRAM 索引,也可以在 JSON 字段中的特定路径上创建。

示例 1:在 VARCHAR 字段上创建

对于 VARCHAR 字段,只需要指定 field_name,并配置 min_grammax_gram

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(uri="http://localhost:19530")  # 替换为你的服务地址

# 假设你在 schema 中定义了一个 VARCHAR 字段 "text"

# 准备索引参数
index_params = client.prepare_index_params()

# 在 "text" 字段上添加 NGRAM 索引
index_params.add_index(
    field_name="text",          # 目标 VARCHAR 字段
    index_type="NGRAM",         # 索引类型
    index_name="ngram_index",   # 索引名称
    min_gram=2,                 # 最小子串长度(如 2-gram: "st")
    max_gram=3                  # 最大子串长度(如 3-gram: "sta")
)

# 在集合上创建索引
client.create_index(
    collection_name="Documents",
    index_params=index_params
)

该配置会:

  • text 字段生成 2-grams 和 3-grams
  • 并将这些 gram 存储在倒排索引中

示例 2:在 JSON 路径上创建

对于 JSON 字段,除了 gram 配置之外,还必须额外指定:

  • params.json_path

    • 指定要索引的 JSON 字段路径
  • params.json_cast_type

    • 必须为 "varchar"(不区分大小写)
    • 因为 NGRAM 索引只能作用于字符串
python 复制代码
# 假设 schema 中定义了 JSON 字段 "json_field",其中包含 key "body"

# 准备索引参数
index_params = client.prepare_index_params()

# 在 JSON 字段上添加 NGRAM 索引
index_params.add_index(
    field_name="json_field",           # 目标 JSON 字段
    index_type="NGRAM",                # 索引类型
    index_name="json_ngram_index",     # 索引名称
    min_gram=2,                        # 最小 n-gram 长度
    max_gram=4,                        # 最大 n-gram 长度
    params={
        "json_path": "json_field[\"body\"]",  # JSON 内部路径
        "json_cast_type": "varchar"           # 必须转换为 varchar
    }
)

# 创建索引
client.create_index(
    collection_name="Documents",
    index_params=index_params
)

在该例中:

  • 只有 json_field["body"] 的值会被索引
  • 在 n-gram 切分前,该值会被转换为 VARCHAR
  • Milvus 会生成长度为 2 到 4 的子串
  • 并将其存入倒排索引

要使 NGRAM 索引生效,需要满足以下条件:

  • 查询字段必须是:

    • 已建立 NGRAM 索引的 VARCHAR 字段,或
    • JSON 路径字段(已配置 NGRAM 索引)
  • LIKE 表达式中的字面子串长度必须 至少为 min_gram

例如:如果你预期最短查询长度为 2 个字符,则在建索引时应设置 min_gram=2

支持的查询类型

  1. 前缀匹配(Prefix match)

    匹配以指定子串开头的字符串:

    text 复制代码
    filter = 'text LIKE "database%"'
  2. 后缀匹配(Suffix match)

    匹配以指定子串结尾的字符串:

    text 复制代码
    filter = 'text LIKE "%database"'
  3. 中缀匹配(Infix match)

    匹配包含指定子串的任意位置:

    text 复制代码
    filter = 'text LIKE "%database%"'
  4. 通配符匹配(Wildcard match)

    Milvus 支持:

    • %:匹配任意长度字符(包括 0 个)
    • _:匹配单个字符

    示例:

    text 复制代码
    filter = 'text LIKE "%st%um%"'

    表示:

    • 字符串中先出现 "st"
    • 之后再出现 "um"
  5. JSON 路径查询

    text 复制代码
    filter = 'json_field["body"] LIKE "%database%"'

可以使用 drop_index() 方法删除已有索引:

python 复制代码
client.drop_index(
    collection_name="Documents",   # 集合名称
    index_name="ngram_index"       # 索引名称
)

使用说明(Usage notes)

  1. 支持字段类型

    • VARCHAR
    • JSON(需额外配置)

    JSON 字段需要同时指定:

    • params.json_path
    • params.json_cast_type = "varchar"
  2. Unicode 支持

    • NGRAM 是基于字符(character-based)的
    • 与语言无关
    • 包含空格和标点符号
  3. 空间与性能权衡

    • [min_gram, max_gram] 范围越大:

      • 生成的 gram 越多
      • 索引越大
      • 内存占用越高

    如果内存不足,可以考虑使用 mmap 模式处理大型 posting list

  4. 不可变性

    • min_grammax_gram 一旦创建后不能修改
    • 如需更改必须重建索引

最佳实践(Best practices)

  1. 合理选择 min_gram 和 max_gram

    建议:

    • 初始设置:

      • min_gram = 2
      • max_gram = 3
    • min_gram

      • 设置为用户可能输入的最短查询长度
    • max_gram

      • 设置为常见有意义子串长度
      • 越大过滤能力越强,但索引越大
  2. 避免低区分度 gram

    例如:

    • "aaaaaa"
    • "xxxxx"

    这种模式区分度低,对过滤帮助有限

  3. 保持一致的文本规范化

    如果业务需要,应确保:

    • 入库文本
    • 查询字符串

    采用一致处理,例如:

    • 小写化(lowercase)
    • 去空格(trim)

RTREE

RTREE 索引是一种基于树结构的数据结构,用于加速 Milvus 中 GEOMETRY(几何)字段的查询。

如果你的集合中存储的是几何对象,例如:

  • 点(point)
  • 线(line)
  • 多边形(polygon)

并且使用的是 WKT(Well-known text)格式,同时需要加速空间过滤查询,那么 RTREE 是一个理想选择。

Milvus 使用 RTREE 索引来高效组织和过滤几何数据,整个过程分为两个阶段:

阶段 1:构建索引(Build index)

  1. 创建叶子节点

    对每个几何对象:

    • 计算其最小外接矩形(MBR,Minimum Bounding Rectangle)
    • MBR 是能够完全包围该几何对象的最小矩形
    • 将该 MBR 作为叶子节点存储
  2. 聚合成更大的矩形

    将相邻的叶子节点进行分组:

    • 每一组用一个新的 MBR 包裹
    • 形成内部节点(internal nodes)

    例如:

    • B 组包含 D 和 E
    • C 组包含 F 和 G
  3. 添加根节点

    • 根节点的 MBR 覆盖所有内部节点
    • 构成一个高度平衡的树结构

阶段 2:加速查询(Accelerate queries)

  1. 构造查询 MBR

    将查询几何对象转换为其 MBR。

  2. 剪枝(Prune branches)

    从根节点开始:

    • 将查询 MBR 与每个内部节点 MBR 比较
    • 如果不相交,则直接剪枝(跳过该分支)
  3. 收集候选集

    • 进入相交的分支
    • 收集候选叶子节点
  4. 精确匹配

    对候选结果进行最终几何谓词判断:

    • 确认真正匹配的几何对象

你可以在 schema 中定义的 GEOMETRY 字段上创建 RTREE 索引。

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(uri="http://localhost:19530")  # 替换为你的服务地址

# 假设 schema 中定义了 GEOMETRY 字段 "geo"

# 准备索引参数
index_params = client.prepare_index_params()

# 在 geo 字段上创建 RTREE 索引
index_params.add_index(
    field_name="geo",
    index_type="RTREE",      # 空间索引类型
    index_name="rtree_geo",  # 索引名称(可选)
    params={}                # 无需额外参数
)

# 创建索引
client.create_index(
    collection_name="geo_demo",
    index_params=index_params
)

你可以在 filter 表达式中使用几何操作符进行过滤。

当 GEOMETRY 字段上存在 RTREE 索引时:

  • Milvus 会自动使用索引进行候选剪枝
  • 如果没有索引,则退化为全表扫描

示例 1:纯过滤查询(Filter only)

查找位于指定多边形内的所有几何对象:

text 复制代码
filter_expr = "ST_CONTAINS(geo, 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))')"

res = client.query(
    collection_name="geo_demo",
    filter=filter_expr,
    output_fields=["id", "geo"],
    limit=10
)

print(res)

预期结果:

返回所有 geo 完全位于该多边形内部的记录。

示例 2:向量搜索 + 空间过滤

查找:

与某条线相交,同时向量最相似的 TopK 结果

text 复制代码
query_vec = [[0.1, 0.2, 0.3, 0.4, 0.5]]
filter_expr = "ST_INTERSECTS(geo, 'LINESTRING (1 1, 2 2)')"

hits = client.search(
    collection_name="geo_demo",
    data=query_vec,
    limit=5,
    filter=filter_expr,
    output_fields=["id", "geo"]
)

print(hits)

预期结果:

返回:

  • 在向量相似度 TopK 中
  • 同时满足 geo 与线相交条件的记录

使用 drop_index() 删除索引:

python 复制代码
client.drop_index(
    collection_name="geo_demo",   # 集合名称
    index_name="rtree_geo"        # 索引名称
)

注意版本差异

  • v2.6.3 及更早版本

    • 删除标量索引前必须 release collection
  • v2.6.4 及以后版本

    • 可直接删除索引
    • 无需 release collection

RTREE 的核心作用是:

用"最小外接矩形 + 树结构剪枝"来加速空间查询

适用于:

  • GIS 数据
  • 空间范围查询
  • 点/线/面过滤
  • 向量 + 空间联合查询

STL_SORT

STL_SORT 索引是一种专门用于提升 Milvus 中字段查询性能的索引类型。

它通过对数据进行排序来加速查询,适用于以下字段类型:

  • 数值字段(INT8、INT16 等)
  • VARCHAR 字段
  • TIMESTAMPTZ 时间戳字段

如果你经常执行以下类型的查询,推荐使用 STL_SORT:

  • 比较过滤:

    • ==
    • !=
    • >
    • <
    • >=
    • <=
  • 范围过滤:

    • IN
    • LIKE

支持的数据类型

  1. 数值字段

    例如:

    • INT8 / INT16 / INT32 / INT64
    • FLOAT / DOUBLE
  2. VARCHAR 字段

  3. TIMESTAMPTZ 字段

STL_SORT 在 Milvus 中分为两个阶段:

  1. 阶段 1:构建索引(Build index)

    在数据写入时:

    1. 收集索引字段的所有值
    2. 使用 C++ STL 的 std::sort 对数据进行升序排序
    3. 将每个值与对应的 entity ID 绑定
    4. 将排序后的数组持久化为索引结构
  2. 阶段 2:加速查询(Accelerate queries)

    查询时:

    1. 使用二分查找(std::lower_bound / std::upper_bound

      在排序数组中快速定位数据

    2. 对不同查询类型处理如下:

    • 等值查询(=)

      • 快速定位所有匹配值
    • 范围查询

      • 找到起始位置和结束位置
      • 返回区间内所有值
    1. 将匹配的 entity ID 交给查询执行器生成最终结果

STL_SORT 将查询复杂度从:

  • O(n)(全表扫描)

降低到:

  • O(log n + m)

其中:

  • m = 命中结果数量

在数值或 TIMESTAMPTZ 字段上创建 STL_SORT 索引,无需额外参数。

以下示例展示在 TIMESTAMPTZ 字段上创建索引:

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(uri="http://localhost:19530")  # 替换为你的服务地址

# 假设 schema 中定义了 TIMESTAMPTZ 字段 "tsz"

# 准备索引参数
index_params = client.prepare_index_params()

# 在 tsz 字段上添加 STL_SORT 索引
index_params.add_index(
    field_name="tsz",
    index_type="STL_SORT",   # 时间字段排序索引
    index_name="tsz_index",  # 索引名称(可选)
    params={}                # 无需额外参数
)

# 创建索引
client.create_index(
    collection_name="tsz_demo",
    index_params=index_params
)

使用 drop_index() 方法删除索引:

python 复制代码
client.drop_index(
    collection_name="tsz_demo",   # 集合名称
    index_name="tsz_index"        # 索引名称
)

使用说明(Usage notes)

  1. 支持字段类型

    支持以下字段类型:

    • 数值类型字段
    • TIMESTAMPTZ 字段

    更多信息参考:

    • Boolean & Number
    • TIMESTAMPTZ Field
  2. 参数

    • STL_SORT 不需要任何索引参数
  3. 不支持 mmap

    • STL_SORT 不支持内存映射(mmap)模式

GPU 索引概览(GPU Index Overview)

在 Milvus 中使用 GPU 构建索引,可以在高吞吐量和高召回率场景下显著提升搜索性能。

下图展示了不同索引配置、硬件环境、向量数据集(Cohere 与 OpenAI)以及搜索 batch size 下的查询吞吐量(QPS)。

结果表明:

GPU_CAGRA 在大多数情况下都明显优于其他方法

Milvus 支持全局 GPU 内存池,并在配置文件中提供两个参数:

  • initMemSize
  • maxMemSize
yaml 复制代码
gpu:
  initMemSize: 0   # GPU 内存池初始大小
  maxMemSize: 0    # GPU 内存最大使用限制

默认情况下,initMemSize 通常是在 Milvus 启动时设为 GPU 内存的一半,而 maxMemSize 默认等于整个 GPU 内存。

GPU 内存池的初始大小被设置为 initMemSize,并会在需要时自动扩展到 maxMemSize

当指定使用 GPU 索引时,Milvus 会在搜索前将目标 collection 的数据加载到 GPU 内存中,因此 maxMemSize 必须至少大于等于数据集的大小。

限制(Limits):

  • 对于 GPU_IVF_FLATlimit 的最大值为 1024

  • 对于 GPU_IVF_PQGPU_CAGRAlimit 的最大值同样为 1024

  • 对于 GPU_BRUTE_FORCE ,虽然没有硬性限制,但建议不要超过 4096,以避免潜在的性能问题。

  • 目前 GPU 索引不支持 COSINE(余弦距离) 。如果需要使用余弦距离,应先对数据进行归一化,然后使用 内积(Inner Product, IP)距离作为替代。

  • GPU 索引的 OOM(内存溢出)保护机制目前并不完善,数据量过大可能会导致 QueryNode 崩溃。

  • GPU 索引也不支持某些查询功能,例如:

    • 范围查询(range search)
    • 分组查询(grouping search)

支持的 GPU 索引类型:

索引类型 描述 内存占用
GPU_CAGRA GPU_CAGRA 是一种基于图结构的 GPU 优化索引。在 Milvus GPU 版本中,使用推理级 GPU 通常比昂贵的训练级 GPU 更具成本优势。 内存占用约为原始向量数据的 1.8 倍
GPU_IVF_FLAT GPU_IVF_FLAT 是最基础的 IVF 索引,每个单元中存储的编码数据与原始数据一致。在查询时,对于 GPU_IVF_FLAT 索引的集合,top-k(limit)最大为 256 需要与原始数据大小相同的内存
GPU_IVF_PQ GPU_IVF_PQ 在进行向量量化前先进行 IVF 聚类。在查询时,对于该索引,top-k(limit)最大可达 8192 内存占用较小,取决于压缩参数配置
GPU_BRUTE_FORCE GPU_BRUTE_FORCE 适用于极高召回率场景,通过将查询向量与所有向量逐一比较,保证召回率为 1。仅需 metric_type 和 top-k(limit)即可完成索引构建和查询。 需要与原始数据大小相同的内存

配置 Milvus 的 GPU 内存控制设置

Milvus 使用全局 GPU 内存池来分配 GPU 内存。它在 Milvus 配置文件中支持两个参数:initMemSizemaxMemSize。内存池的初始大小设置为 initMemSize,当内存使用量超过该值时,会自动扩展至 maxMemSize

默认情况下,initMemSize 为 Milvus 启动时可用 GPU 内存的 1/2,maxMemSize 默认为全部可用 GPU 内存。

在 Milvus 2.4.1 之前,Milvus 使用统一的 GPU 内存池。对于 2.4.1 之前的版本,建议将这两个参数都设置为 0。

yaml 复制代码
gpu:
  initMemSize: 0 # 设置初始内存池大小
  maxMemSize: 0  # 设置最大内存使用限制。当内存使用量超过 initMemSize 时,Milvus 会尝试扩展内存池

从 Milvus 2.4.1 开始,GPU 内存池仅用于搜索过程中的临时 GPU 数据。因此,建议将其设置为 2048 和 4096。

yaml 复制代码
gpu:
  initMemSize: 2048 # 设置初始内存池大小
  maxMemSize: 4096  # 设置最大内存使用限制。当内存使用量超过 initMemSize 时,Milvus 会尝试扩展内存池

什么时候适合使用 GPU 索引?

GPU 索引特别适用于对高吞吐量(high throughput)或高召回率(high recall)有要求的场景。

例如,在处理大批量查询时,GPU 索引的吞吐量最高可达到 CPU 索引的 100 倍。在较小批量的查询场景中,GPU 索引在性能方面仍然显著优于 CPU 索引。此外,如果需要快速插入数据,使用 GPU 还能够大幅加快索引构建过程。

GPU_CAGRA、GPU_IVF_PQ、GPU_IVF_FLAT 和 GPU_BRUTE_FORCE 分别适用于哪些场景?

GPU_CAGRA 适用于对性能要求较高的场景,但代价是需要消耗更多内存。

对于需要优先节省内存的环境,可以使用 GPU_IVF_PQ 索引来降低存储占用,但其精度损失会相对更大。

GPU_IVF_FLAT 是一种较为均衡的选择,在性能和内存使用之间提供折中方案。

GPU_BRUTE_FORCE 则适用于需要穷举搜索(exhaustive search)的场景。它通过遍历搜索保证召回率达到 1(Recall = 1)。

GPU_CAGRA

GPU_CAGRA 是一种针对 GPU 优化的图索引(graph-based index)。与价格昂贵的训练级 GPU 相比,使用推理级 GPU 运行 Milvus GPU 版本通常具有更高的性价比。

要在 Milvus 中为向量字段构建 GPU_CAGRA 索引,可以使用 add_index() 方法,并指定 index_typemetric_type 以及相关索引参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name", # 待建立索引的向量字段名称
    index_type="GPU_CAGRA", # 要创建的索引类型
    index_name="vector_index", # 索引名称
    metric_type="L2", # 相似度度量方式
    params={
        "intermediate_graph_degree": 64, # 决定剪枝前图的度数,影响召回率和构建时间
        "graph_degree": 32, # 决定剪枝后图的度数,影响搜索性能和召回率
        "build_algo": "IVF_PQ", # 指定剪枝前用于生成图的算法
        "cache_dataset_on_device": "true", # 是否将原始数据集缓存到 GPU 显存中
        "adapt_for_cpu": "false", # 是否使用 GPU 构建索引、CPU 执行搜索
    } # 索引构建参数
)

在此配置中:

  • index_type :要构建的索引类型。本例中设置为 GPU_CAGRA
  • metric_type :用于计算向量距离的方法。详情请参见 Metric Types
  • params :索引构建时的附加配置项。关于 GPU_CAGRA 支持的更多构建参数,请参见 Index building params

索引构建完成并插入数据后,即可执行相似度搜索。

python 复制代码
search_params = {
    "params": {
        "itopk_size": 16, # 搜索过程中保留的中间结果数量
        "search_width": 8, # 搜索时进入 CAGRA 图的入口点数量
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name", # Collection 名称
    anns_field="vector_field", # 向量字段名称
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],  # 查询向量
    limit=3,  # 返回 TopK 结果数
    search_params=search_params
)

在此配置中:

  • params :搜索阶段的附加配置项。关于 GPU_CAGRA 支持的更多搜索参数,请参见 Index-specific search params

如果希望在加载索引时动态启用 CPU 搜索,可以在 milvus.yaml 中添加如下配置:

yaml 复制代码
# milvus.yaml
knowhere:
  GPU_CAGRA:
    load:
      adapt_for_cpu: true

load.adapt_for_cpu 设置为 true 时:

  • Milvus 会在加载阶段将 GPU_CAGRA 索引转换为可在 CPU 上执行的格式(类似 HNSW)。
  • 后续搜索请求将在 CPU 上执行,即使该索引最初是在 GPU 上构建的。
  • 如果未配置该项或设置为 false,索引将继续保留在 GPU 上,搜索也将在 GPU 上执行。

在混合部署环境或对成本敏感的场景下,可以使用这种"加载时 CPU 适配"模式:GPU 专门用于索引构建,而查询搜索则由 CPU 执行。

下表列出了构建索引时可在 params 中配置的参数。

参数 描述 默认值
intermediate_graph_degree 决定剪枝(pruning)前图的度数(degree),影响召回率(recall)和索引构建时间。推荐值为 32 或 64。 128
graph_degree 决定剪枝后图的度数,影响搜索性能和召回率。graph_degreeintermediate_graph_degree 的差值越大,索引构建时间越长。其值必须小于 intermediate_graph_degree 64
build_algo 指定剪枝前用于生成图的算法。可选值: • IVF_PQ :索引质量更高,但构建速度较慢。 • NN_DESCENT:构建速度更快,但召回率可能较低。 IVF_PQ
cache_dataset_on_device 是否将原始数据集缓存到 GPU 显存中。可选值: • "true":缓存原始数据集,通过结果精排(refine)提升召回率。 • "false":不缓存原始数据集,以节省 GPU 显存。 "false"
adapt_for_cpu 是否使用 GPU 构建索引、CPU 执行搜索。 当设置为 "true" 时,搜索请求中必须提供 ef 参数。 "false"

下表列出了搜索时可在 search_params.params 中配置的参数。

参数 描述 默认值
itopk_size 搜索过程中保留的中间结果数量。较大的值通常可以提高召回率,但会降低搜索性能。其值应至少等于最终的 top-k(即 limit),通常设置为 2 的幂,例如 16、32、64、128。
search_width 搜索时进入 CAGRA 图的入口点数量。增大该值可以提高召回率,但可能降低搜索性能。例如:1、2、4、8、16、32。
min_iterations / max_iterations 控制搜索迭代过程。默认值均为 0,此时 CAGRA 会根据 itopk_sizesearch_width 自动确定迭代次数。手动调整这两个参数可以在性能和精度之间进行权衡。 0
team_size 指定 GPU 上用于计算距离的 CUDA 线程数。常见取值为不超过 32 的 2 的幂,例如:2、4、8、16、32。对搜索性能影响较小。默认值为 0,此时 Milvus 会根据向量维度自动选择合适的 team_size 0
ef 控制查询速度与搜索精度之间的权衡。ef 越大,搜索结果越准确,但搜索速度越慢。 如果构建索引时将 adapt_for_cpu 设置为 true,则该参数为必填项。 [top_k, int_max]

GPU_IVF_FLAT

GPU_IVF_FLAT 是 IVF_FLAT 索引的 GPU 加速版本,专门为 GPU 环境设计。

它将向量数据划分为 nlist 个聚类(cluster)单元,并通过先将查询向量与各聚类中心进行比较来计算相似度。通过调节 nprobe 参数,仅搜索最有可能包含目标结果的聚类,从而在保证准确率与搜索速度平衡的同时减少查询时间。

要在 Milvus 中为向量字段构建 GPU_IVF_FLAT 索引,可以使用 add_index() 方法,并指定 index_typemetric_type 以及索引参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name", # 待建立索引的向量字段名称
    index_type="GPU_IVF_FLAT", # 要创建的索引类型
    index_name="vector_index", # 索引名称
    metric_type="L2", # 用于计算相似度的距离度量方式
    params={
        "nlist": 1024, # 索引聚类数量
    } # 索引构建参数
)

在此配置中:

  • index_type :要构建的索引类型。本例中设置为 GPU_IVF_FLAT

  • metric_type :用于计算向量距离的方法。详情请参见 Metric Types

  • params:索引构建时的附加配置项。

    • nlist:用于划分数据集的聚类数量。

索引构建完成并插入数据后,即可执行相似度搜索。

python 复制代码
search_params = {
    "params": {
        "nprobe": 10, # 要搜索的聚类数量
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name", # Collection 名称
    anns_field="vector_field",
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],  # 查询向量
    limit=3,  # 返回 TopK 结果数
    search_params=search_params
)

在此配置中:

  • params:搜索阶段的附加配置项。

    • nprobe:搜索时需要扫描的聚类数量。

下表列出了在构建索引时可在 params 中配置的参数。

参数 描述 取值范围 调优建议
nlist 在构建索引过程中,使用 k-means 算法生成的聚类数量。每个聚类由一个中心点(质心)表示,并存储一组向量。增大该参数会减少每个聚类中的向量数量,从而形成更小、更聚焦的分区。 类型:整数 范围:1, 65536 默认值:128 较大的 nlist 值通过生成更细粒度的聚类可以提升召回率,但会增加索引构建时间。建议根据数据集规模和可用资源进行优化。在大多数情况下,推荐设置在范围:32, 4096 内。

下表列出了在索引搜索时可在 search_params.params 中配置的参数。

参数 描述 取值范围 调优建议
nprobe 在搜索候选时需要检索的聚类数量。该值越大,表示会搜索更多聚类,从而扩大搜索范围,提高召回率,但同时也会增加查询延迟。 类型:整数 范围:1, nlist 默认值:8 增大该值可以提升召回率,但可能降低搜索速度。建议根据 nlist 进行比例设置,以在速度与准确性之间取得平衡。通常建议设置在范围:1, nlist 内。

GPU_IVF_PQ

GPU_IVF_PQ 索引是在 IVF_PQ 概念基础上构建的,它结合了倒排文件(Inverted File, IVF)聚类与乘积量化(Product Quantization, PQ)。PQ 会将高维向量拆分为多个子空间,并对其进行量化,从而实现高效的相似性搜索。

GPU_IVF_PQ 专为 GPU 环境设计,利用并行计算能力加速计算过程,能够高效处理大规模向量数据。

要在 Milvus 中的向量字段上构建 GPU_IVF_PQ 索引,可以使用 add_index() 方法,并指定 index_typemetric_type 以及其他索引参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",  # 要建立索引的向量字段名称
    index_type="GPU_IVF_PQ",              # 索引类型
    index_name="vector_index",            # 索引名称
    metric_type="L2",                     # 用于计算相似度的度量方式
    params={
        "m": 4,  # 将每个向量拆分成的子向量数量
    }  # 索引构建参数
)

配置说明

  • index_type :要构建的索引类型。本例中为 GPU_IVF_PQ
  • metric_type :用于计算向量之间距离的方法。支持 COSINEL2IP。更多信息请参考 Metric Types。
  • params:索引构建的附加配置参数。
  • m:将向量拆分成的子向量数量(子空间数量)。

索引构建完成并插入数据后,即可在索引上执行相似性搜索。

python 复制代码
search_params = {
    "params": {
        "nprobe": 10,  # 搜索的聚类数量
    }
}

res = MilvusClient.search(
    collection_name="your_collection_name",  # 集合名称
    anns_field="vector_field",              # 向量字段名称
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],        # 查询向量
    limit=3,                                 # 返回 TopK 结果数量
    search_params=search_params
)

配置说明

  • params:索引搜索的附加配置参数。
  • nprobe:搜索时需要检索的聚类数量。

下表列出了在构建索引时可在 params 中配置的参数。

IVF

参数 描述 取值范围 调优建议
nlist 在索引构建过程中使用 k-means 算法生成的聚类数量。 类型:整数 范围:1, 65536 默认值:128 较大的 nlist 值可以通过生成更精细的聚类提升召回率,但会增加索引构建时间。应根据数据集规模和可用资源进行优化。在大多数情况下,推荐设置在范围:32, 4096 内。

PQ

参数 描述 取值范围 调优建议
m 在量化过程中,将每个高维向量拆分成的子向量数量(用于量化)。 类型:整数 范围:1, 65536 默认值:无 较大的 m 值可以提升精度,但会增加计算复杂度和内存消耗。m 必须是向量维度(D)的因子,以确保能够正确分解。通常推荐值为 m = D/2。在大多数情况下,建议取值范围为:D/8, D
nbits 用于表示每个子向量量化后质心索引的比特数,直接决定码本大小。每个码本包含 (2^{nbits}) 个质心。例如当 nbits=8 时,每个子向量用 8-bit 索引表示,该子空间对应 256 个可能的质心。 类型:整数 范围:1, 24 默认值:8 较大的 nbits 可以提供更大的码本,从而更精确地表示原始向量,但会增加存储开销、降低压缩率。一般建议范围为:1, 16
cache_dataset_on_device 是否将原始数据集缓存到 GPU 显存中。可选值: • "true":缓存原始数据集,提高召回率(通过优化检索结果) • "false":不缓存,以节省 GPU 显存 类型:字符串 范围:"true", "false" 默认值:"false" 设置为 "true" 可通过优化检索提升召回率,但会占用更多 GPU 显存;设置为 "false" 可节省显存资源。

下表列出了在索引搜索时可在 search_params.params 中配置的参数。

IVF

参数 描述 取值范围 调优建议
nprobe 搜索候选时需要检索的聚类数量。 类型:整数 范围:1, nlist 默认值:8 较大的 nprobe 会扩大搜索范围,从而提高召回率,但会增加查询延迟。建议根据 nlist 按比例设置,在速度与准确性之间取得平衡。通常推荐范围为:1, nlist

GPU_BRUTE_FORCE

GPU_BRUTE_FORCE 索引专为 GPU 环境设计,适用于对精度要求极高且不允许任何误差的场景。它通过将查询向量与数据集中所有向量进行逐一穷举比对,保证召回率为 1,从而确保不会遗漏任何潜在匹配结果。

借助 GPU 加速,GPU_BRUTE_FORCE 适用于需要在向量相似性搜索中实现绝对精确性的应用场景。

要在 Milvus 的向量字段上构建 GPU_BRUTE_FORCE 索引,可以使用 add_index() 方法,并仅需指定 index_typemetric_type 参数。

python 复制代码
from pymilvus import MilvusClient

# 准备索引构建参数
index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name",  # 要建立索引的向量字段名称
    index_type="GPU_BRUTE_FORCE",         # 索引类型
    index_name="vector_index",            # 索引名称
    metric_type="L2",                     # 用于计算相似度的度量方式
    params={}                             # GPU_BRUTE_FORCE 不需要额外参数
)

配置说明

  • index_type :要构建的索引类型。本例中为 GPU_BRUTE_FORCE
  • metric_type:用于计算向量距离的方法。详细说明请参考 Metric Types。
  • params:GPU_BRUTE_FORCE 索引不需要任何额外参数。

索引构建并插入数据后,即可在该索引上执行相似性搜索。

python 复制代码
res = MilvusClient.search(
    collection_name="your_collection_name",  # 集合名称
    anns_field="vector_field",              # 向量字段名称
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],        # 查询向量
    limit=3,                                 # 返回 TopK 结果数量
    search_params={"params": {}}             # GPU_BRUTE_FORCE 不需要额外参数
)

对于 GPU_BRUTE_FORCE 索引,无论是在索引构建阶段还是搜索阶段,都不需要配置任何额外参数。

总结

迄今为止milvus 这一套已经不是"数据库索引",而是**"检索系统 + 论文算法工程化集合"**,它本质更接近 vector search engine / ANN system,而不是 MySQL 那种关系型索引体系。

但它其实是"看起来复杂,本质很收敛"的。

先纠正一个关键认知:Milvus 不是在替代 B+Tree

MySQL 的 B+Tree 做的是:

精确查找 / 范围查找(ordered key lookup)

而 Milvus 做的是:

近似最近邻搜索(ANN, Approximate Nearest Neighbor)

所以它们根本不是一类问题:

系统 解决的问题 核心结构
MySQL where id = ? / range scan B+Tree / Hash
Milvus 找"最像的向量" 图 / 聚类 / 量化 / 哈希 / brute force

Milvus 索引其实只有 4 大思想(核心!)

几十种 index,本质可以归为 4 类思想:

① 穷举类(Exact Search)

代表:

  • FLAT
  • GPU_BRUTE_FORCE

核心:

不做任何索引优化,直接全量算距离

特点:

  • 100% recall
  • O(N) 计算
  • GPU 加速只是"算得更快"

👉 本质:暴力扫描

② 聚类类(IVF 系列)

代表:

  • IVF_FLAT
  • IVF_PQ
  • GPU_IVF_FLAT
  • GPU_IVF_PQ
  • IVF_SQ8

核心思想:

先分桶(cluster),只搜部分桶

结构:

复制代码
向量空间
   ↓ k-means
  nlist个桶(cluster)
   ↓
查询 → 只搜 nprobe 个桶

特点:

  • 可控 recall
  • 工程上最常用

👉 本质:"先分组,再局部搜索"

③ 图结构(Graph ANN)

代表:

  • HNSW
  • DISKANN
  • SCANN
  • GPU_CAGRA

核心思想:

用"邻居关系图"做跳跃搜索

结构:

复制代码
node ←→ node ←→ node
  ↘         ↙
     node

搜索:

从入口点开始"贪心跳图"

特点:

  • 高 recall + 高性能
  • 内存占用较大
  • 工业主流(HNSW 最常见)

👉 本质:"用图做导航搜索"

④ 量化 / 压缩类(PQ / SQ / PRQ)

代表:

  • IVF_PQ
  • HNSW_PQ
  • IVF_SQ8
  • AISAQ

核心思想:

向量太大 → 压缩再算

例子:

复制代码
原向量: 768维 float32
↓
PQ:
拆成 8~64段
每段用 codebook 表示
↓
变成 compact code

特点:

  • 省内存
  • 更快
  • 牺牲一点精度

👉 本质:"用编码替代原始向量"

Milvus 的 index 不是"不同算法",而是:✔ 三个维度在组合:

1️⃣ 搜索方式

  • brute force
  • clustering (IVF)
  • graph
  • hashing (MinHash / LSH)

2️⃣ 存储方式

  • raw (FLAT)
  • compressed (PQ / SQ / PRQ)
  • disk-based (DISKANN)

3️⃣ 硬件加速

  • CPU
  • GPU
  • hybrid

👉 所以 GPU_IVF_PQ =

复制代码
GPU + 聚类 + PQ压缩

metric type(距离函数)其实只有"3种本质"

① 距离型(越小越像)

  • L2
  • HAMMING
  • JACCARD
  • MHJACCARD

👉 本质:

复制代码
distance(a,b) → 0 = 越像

② 相似度型(越大越像)

  • IP
  • COSINE

👉 本质:

复制代码
dot product / angle similarity

③ 概率/文本相关

  • BM25

👉 本质:

不是向量距离,是信息检索模型(IR)

如果把 Milvus 压缩成一句话:

Milvus = 在高维空间里做"快速近似最近邻搜索"的工程系统

然后所有东西都只是:

✔ Step 1:减少候选集

方法:

  • IVF(聚类)
  • HNSW(图)
  • LSH(哈希)

✔ Step 2:压缩向量

方法:

  • PQ
  • SQ8
  • PRQ

✔ Step 3:加速计算

方法:

  • GPU
  • SIMD
  • disk indexing

✔ Step 4:定义"相似"

方法:

  • L2 / IP / COSINE
  • HAMMING / JACCARD
  • BM25(文本)

mivlus的复杂体系,其实可以压成一张"心智图":

复制代码
         Vector Search
              │
  ┌───────────┼────────────┐
  │           │            │
Candidate   Compression   Metric
Reduction     (PQ)       (distance)
  │
  ├─ IVF (cluster)
  ├─ HNSW (graph)
  ├─ LSH (hash)
  └─ BRUTE (none)

如果你想真正吃透 Milvus,而不是死记 index只需要掌握 3 个核心问题:

  1. 数据怎么被"缩小搜索范围"?(IVF / HNSW)

  2. 数据怎么被"压缩存储"?(PQ / SQ)

  3. 相似性怎么被"定义"?(metric)

搜索

基础 ANN 搜索(Basic ANN Search)

基于记录向量嵌入排序信息的索引文件,近似最近邻(Approximate Nearest Neighbor,ANN)搜索能够根据搜索请求中携带的查询向量,快速定位一部分可能最相似的向量,再对这一子集进行相似度计算,并返回最相似的结果。

借助 ANN 搜索,Milvus 能够提供高效的向量检索能力。本节将介绍如何执行基础的 ANN 搜索。

注意

如果在集合(Collection)创建完成之后动态新增字段,那么对于未显式设置这些字段值的实体,在搜索时:

  • 如果字段定义了默认值,则返回默认值;
  • 否则返回 NULL

在向量相似性检索中,最常见的两种搜索方式是:

  • k 最近邻搜索(k-Nearest Neighbors,kNN)
  • 近似最近邻搜索(Approximate Nearest Neighbor,ANN)

kNN 搜索中,需要将查询向量与整个向量空间中的所有向量逐一进行比较,然后才能找出最相似的结果。因此:

  • 计算耗时较长;
  • 计算资源消耗较大。

与 kNN 不同,ANN 搜索依赖于提前构建好的索引。

索引中保存了向量嵌入的组织或排序信息。当收到搜索请求时,可以借助索引快速定位一个极有可能包含目标向量的候选子集,然后:

  1. 使用指定的距离度量(Metric Type)计算查询向量与候选向量之间的相似度;
  2. 根据相似度对候选向量排序;
  3. 返回相似度最高的 Top-K 个结果。

由于 ANN 搜索依赖预先构建的索引,因此:

  • 搜索吞吐量(Throughput)
  • 内存占用(Memory Usage)
  • 搜索准确率(Correctness / Recall)

都会随着所选索引类型的不同而有所差异。

因此,需要在搜索性能搜索准确率之间进行权衡。

为了降低学习成本,Milvus 提供了 AUTOINDEX

使用 AUTOINDEX 时,Milvus 会在构建索引过程中自动分析集合中的数据分布,并据此选择最优的索引参数,以尽可能在搜索性能和搜索准确率之间取得平衡。

在 ANN 搜索中,单向量搜索(Single-vector search)指的是一次搜索请求中仅包含一个查询向量

Milvus 会依据:

  • 已构建好的索引;
  • 搜索请求中指定的距离度量(Metric Type);

找出与查询向量最相似的 Top-K 个向量。

下面示例中:

  • 搜索请求仅包含一个查询向量;
  • 使用 Inner Product(IP,内积) 作为相似度计算方式;
  • 返回最相似的 3 条结果。
python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

# 4. 单向量搜索
query_vector = [
    0.3580376395471989,
    -0.6023495712049978,
    0.18414012509913835,
    -0.26286205330961354,
    0.9029438446296592
]

res = client.search(
    collection_name="quick_setup",
    anns_field="vector",
    data=[query_vector],
    limit=3,
    search_params={"metric_type": "IP"}
)

for hits in res:
    for hit in hits:
        print(hit)

输出示例:

python 复制代码
[
    [
        {
            "id": 551,
            "distance": 0.08821295201778412,
            "entity": {}
        },
        {
            "id": 296,
            "distance": 0.0800950899720192,
            "entity": {}
        },
        {
            "id": 43,
            "distance": 0.07794742286205292,
            "entity": {}
        }
    ]
]

Milvus 会按照查询向量与候选向量之间的**相似度分数(Similarity Score)**进行降序排序。

这里返回的 distance 字段表示查询向量与结果向量之间的距离(Distance),也可理解为相似度得分。

需要注意的是,不同距离度量(Metric Type)下,distance 的含义及取值范围不同。

Metric Type 特性 Distance 范围
L2 值越小,相似度越高 [0, +∞)
IP(Inner Product) 值越大,相似度越高 [-1, 1]
COSINE 值越大,相似度越高 [-1, 1]
JACCARD 值越小,相似度越高 [0, 1]
HAMMING 值越小,相似度越高 [0, dim(vector)]

说明:

  • L2:欧氏距离,距离越小表示越接近。
  • IP:内积,相同方向且长度较大的向量得分更高。
  • COSINE:余弦相似度,只考虑向量方向,不受向量长度影响。
  • JACCARD:用于集合或二值数据的相似性计算。
  • HAMMING:用于二进制向量,表示两个向量中不同位的数量。

同样地,你也可以在一次搜索请求中包含多个查询向量(Query Vector)。Milvus 会并行地对这些查询向量执行 ANN(Approximate Nearest Neighbor,近似最近邻)搜索,并返回多组搜索结果。

python 复制代码
# 7. 使用多个向量进行搜索

# 7.1 准备查询向量
query_vectors = [
    [0.041732933, 0.013779674, -0.027564144, -0.013061441, 0.009748648],
    [0.0039737443, 0.003020432, -0.0006188639, 0.03913546, -0.00089768134]
]

# 7.2 发起搜索
res = client.search(
    collection_name="quick_setup",
    data=query_vectors,
    limit=3,
)

for hits in res:
    print("TopK results:")
    for hit in hits:
        print(hit)

# 输出
#
# [
#     [
#         {
#             "id": 551,
#             "distance": 0.08821295201778412,
#             "entity": {}
#         },
#         {
#             "id": 296,
#             "distance": 0.0800950899720192,
#             "entity": {}
#         },
#         {
#             "id": 43,
#             "distance": 0.07794742286205292,
#             "entity": {}
#         }
#     ],
#     [
#         {
#             "id": 730,
#             "distance": 0.04431751370429993,
#             "entity": {}
#         },
#         {
#             "id": 333,
#             "distance": 0.04231833666563034,
#             "entity": {}
#         },
#         {
#             "id": 232,
#             "distance": 0.04221535101532936,
#             "entity": {}
#         }
#     ]
# ]

主键搜索(Primary-Key Search)兼容版本:Milvus 2.6.9+

除了直接提供查询向量之外,如果这些查询向量已经存在于目标 Collection 中,你还可以直接使用主键(Primary Key)进行搜索,而无需再次传入向量数据。

python 复制代码
res = client.search(
    collection_name="quick_setup",
    anns_field="vector",
    ids=[551, 296, 43],
    limit=3,
    search_params={"metric_type": "IP"}
)

for hits in res:
    for hit in hits:
        print(hit)

假设你的 Collection 中创建了多个 Partition(分区),那么你可以将搜索范围限制到一个或多个指定的 Partition。

只需在搜索请求中指定目标分区名称,即可使搜索仅在这些 Partition 内执行。由于参与搜索的数据范围缩小,因此可以进一步提升搜索性能。

下面的示例假设 Collection 中存在一个名为 partitionA 的分区。

python 复制代码
# 单向量搜索
query_vector = [
    0.3580376395471989,
    -0.6023495712049978,
    0.18414012509913835,
    -0.26286205330961354,
    0.9029438446296592
]

res = client.search(
    collection_name="quick_setup",
    partition_names=["partitionA"],
    data=[query_vector],
    limit=3,
)

for hits in res:
    print("TopK results:")
    for hit in hits:
        print(hit)

# 输出
# [
#     [
#         {
#             "id": 551,
#             "distance": 0.08821295201778412,
#             "entity": {}
#         },
#         {
#             "id": 296,
#             "distance": 0.0800950899720192,
#             "entity": {}
#         },
#         {
#             "id": 43,
#             "distance": 0.07794742286205292,
#             "entity": {}
#         }
#     ]
# ]

默认情况下,在搜索结果中,Milvus 仅返回包含 Top-K 向量的实体的主键字段(Primary Key)以及相似度距离/得分(Distance/Score)

如果希望搜索结果同时返回实体中的其他字段值(包括向量字段标量字段 ),可以在搜索请求中通过 output_fields 参数指定需要返回的字段名称。

python 复制代码
# 4. 单向量搜索
query_vector = [
    0.3580376395471989,
    -0.6023495712049978,
    0.18414012509913835,
    -0.26286205330961354,
    0.9029438446296592,
]

res = client.search(
    collection_name="quick_setup",
    data=[query_vector],
    limit=3,  # 返回结果数量
    search_params={"metric_type": "IP"},
    output_fields=["color"]
)

print(res)

# 输出
# [
#     [
#         {
#             "id": 551,
#             "distance": 0.08821295201778412,
#             "entity": {
#                 "color": "orange_6781"
#             }
#         },
#         {
#             "id": 296,
#             "distance": 0.0800950899720192,
#             "entity": {
#                 "color": "red_4794"
#             }
#         },
#         {
#             "id": 43,
#             "distance": 0.07794742286205292,
#             "entity": {
#                 "color": "grey_8510"
#             }
#         }
#     ]
# ]

默认情况下,Milvus 会按照查询向量与结果向量之间的**相似度得分(Similarity Score)**对搜索结果进行排序。

如果希望返回的实体按照某个**标量字段(Scalar Field)**排序,可以在搜索请求中添加 order_by_fields 参数。

order_by_fields 中的每一项都用于指定一个排序字段及排序方向:

  • "asc":升序(Ascending)
  • "desc":降序(Descending)

如果省略 order 参数,Milvus 默认采用升序排序

下面的示例按照 price 字段从低到高排序搜索结果。

如果希望在返回结果中查看排序字段的值,需要将该字段同时加入 output_fields

python 复制代码
res = client.search(
    collection_name="product_catalog",
    data=query_vectors,
    anns_field="embedding",
    limit=20,
    output_fields=["id", "price", "rating", "category"],
    order_by_fields=[
        {"field": "price", "order": "asc"}
    ],
)

你也可以按照多个标量字段进行排序。

Milvus 会按照 order_by_fields 中字段出现的顺序依次排序。

下面的示例中:

  1. 首先按照 price 升序排序;
  2. 如果多个实体的 price 相同,则继续按照 rating 降序排序。
python 复制代码
res = client.search(
    collection_name="product_catalog",
    data=query_vectors,
    anns_field="embedding",
    limit=20,
    output_fields=["id", "price", "rating", "category"],
    order_by_fields=[
        {"field": "price", "order": "asc"},
        {"field": "rating", "order": "desc"},
    ],
)

如果多个实体在所有指定的排序字段上的值都完全相同,Milvus 会继续保持它们按照相似度得分的原始排序顺序。

你可能已经注意到,搜索请求中的 limit 参数决定了搜索结果中返回多少个实体。

该参数表示单次搜索最多返回的实体数量 ,通常也称为 Top-K

如果需要实现分页查询(Pagination) ,可以循环发送多个 Search 请求,并在每次请求中设置不同的 limitoffset 参数。

其中:

  • limit:当前页需要返回的实体数量。
  • offset:跳过前面已经返回的实体数量。

例如,每页返回 100 条数据时,各次查询参数如下:

查询次数 每次返回数量(Limit) 已返回总数(Offset)
第 1 次查询 100 0
第 2 次查询 100 100
第 3 次查询 100 200
第 n 次查询 100 100 × (n − 1)

注意:

在一次 ANN 搜索中,limit + offset 的总和必须小于 16,384

示例:

python 复制代码
# 4. 单向量搜索
query_vector = [
    0.3580376395471989,
    -0.6023495712049978,
    0.18414012509913835,
    -0.26286205330961354,
    0.9029438446296592,
]

res = client.search(
    collection_name="quick_setup",
    data=[query_vector],
    limit=3,  # 返回结果数量
    search_params={
        "metric_type": "IP",
        "offset": 10  # 跳过前 10 条记录
    }
)

如果你的 Collection 包含 TIMESTAMPTZ 字段,可以在一次搜索操作中,通过 timezone 参数临时覆盖数据库或 Collection 的默认时区。

该设置仅对当前搜索请求生效,用于控制 TIMESTAMPTZ 字段值在本次操作中的显示方式比较方式

timezone 参数的值必须是一个合法的 IANA 时区标识符,例如:

  • Asia/Shanghai
  • America/Chicago
  • UTC

有关 TIMESTAMPTZ 字段的详细用法,请参考 TIMESTAMPTZ Field

下面的示例演示了如何在一次搜索操作中临时指定时区:

python 复制代码
res = client.search(
    collection_name="quick_setup",
    anns_field="vector",
    data=[query_vector],
    limit=3,
    search_params={"metric_type": "IP"},
    timezone="America/Havana",
)

虽然 AUTOINDEX 大大降低了 ANN(Approximate Nearest Neighbor,近似最近邻)搜索的使用门槛,但随着 Top-K 值不断增大,搜索结果未必始终准确。

为了进一步提高搜索质量,Milvus 提供了多种搜索增强能力,包括:

  • 缩小搜索范围;
  • 提高搜索结果的相关性;
  • 增强搜索结果的多样性。

主要包括以下功能。

  1. 过滤搜索(Filtered Search)

    你可以在搜索请求中添加过滤条件,使 Milvus 先执行元数据过滤(Metadata Filtering),再进行 ANN 搜索。

    这样可以将搜索范围从整个 Collection 缩小到仅满足过滤条件的实体,从而提高搜索效率并提升结果的相关性。

  2. 范围搜索(Range Search)

    你可以通过限制返回实体的**距离(Distance)相似度得分(Score)**范围来提高搜索结果的相关性。

    在 Milvus 中,范围搜索的原理是:

    以与查询向量最相似的向量为圆心,构建两个同心圆。

    搜索请求中需要指定两个圆的半径,Milvus 将返回:

    • 位于外圆之内;
    • 同时位于内圆之外;

    的所有向量。

  3. 分组搜索(Grouping Search)

    如果返回结果中的多个实体在某个字段上具有相同的值,那么这些结果可能无法充分反映整个向量空间中的分布情况。

    为了提高搜索结果的多样性,可以使用分组搜索(Grouping Search)

  4. 混合搜索(Hybrid Search)

    一个 Collection 可以包含多个向量字段,每个字段可以保存由不同 Embedding 模型生成的向量。

    利用这一特性,可以执行混合搜索(Hybrid Search) ,对多个向量字段的搜索结果进行重新排序(Rerank),从而提升搜索的召回率(Recall)

  5. 搜索迭代器(Search Iterator)

    一次 ANN 搜索最多只能返回 16,384 个实体。

    如果需要获取更多结果,可以使用搜索迭代器(Search Iterator)

  6. 全文搜索(Full-Text Search)

    全文搜索(Full-Text Search)用于在文本数据集中检索包含特定词语或短语的文档,并根据相关性对结果进行排序。

    相比纯语义搜索,它具有以下优势:

    • 能够准确匹配指定关键词,避免语义搜索遗漏精确词汇;
    • 提供更加准确且符合上下文的搜索结果;
    • 支持直接输入原始文本,无需用户手动生成向量;
    • 系统会自动将文本转换为稀疏向量(Sparse Embedding),简化向量搜索流程。
  7. 关键词匹配(Text Match)

    Milvus 提供的关键词匹配(Keyword Match)支持根据指定关键词精确检索文档。

    该功能主要用于过滤搜索,可满足特定条件下的查询需求,并支持结合标量字段过滤(Scalar Filtering),从而在满足标量条件的数据范围内执行向量相似度搜索。

  8. 使用分区键(Use Partition Key)

    如果元数据过滤涉及多个标量字段,并且过滤条件较为复杂,可能会影响搜索效率。

    将某个标量字段设置为**分区键(Partition Key)**后,在搜索请求中使用该字段进行过滤,可以将搜索范围限制到对应的 Partition,从而进一步提升搜索性能。

  9. 使用 mmap(Use mmap)

    有关 mmap 配置及使用方法,请参阅 Use mmap

  10. 聚类压缩(Clustering Compaction)

    有关聚类压缩(Clustering Compaction)的详细介绍,请参阅 Clustering Compaction

  11. 使用重排序(Use Reranking)

    你可以使用各种排序器(Ranker)对搜索结果进行重新排序,以进一步提高搜索结果的相关性。

过滤搜索(Filtered Search)

ANN(Approximate Nearest Neighbor,近似最近邻)搜索用于查找与指定查询向量最相似的向量。

然而,仅依赖向量相似度得到的搜索结果并不一定完全符合业务需求。

因此,你可以在搜索请求中添加过滤条件(Filtering Conditions) ,使 Milvus 在执行 ANN 搜索之前先进行元数据过滤(Metadata Filtering),从而将搜索范围从整个 Collection 缩小到仅满足过滤条件的实体。

在 Milvus 中,根据过滤发生的阶段不同,过滤搜索分为两种方式:

  • 标准过滤(Standard Filtering)
  • 迭代过滤(Iterative Filtering)

标准过滤(Standard Filtering):

如果 Collection 中既包含向量数据,也包含对应的元数据,那么可以在执行 ANN 搜索之前先对元数据进行过滤,以提高搜索结果的相关性。

当 Milvus 收到带有过滤条件的搜索请求后,会首先根据过滤条件筛选符合要求的实体,然后仅在这些实体中执行 ANN 搜索。

其流程如下图所示:

搜索请求中包含如下过滤条件:

text 复制代码
chunk LIKE "%red%"

表示 Milvus 仅在 chunk 字段包含 red 的实体中执行 ANN 搜索。

具体执行流程如下:

  1. 根据搜索请求中的过滤条件筛选符合要求的实体。
  2. 在筛选后的实体范围内执行 ANN 搜索。
  3. 返回 Top-K 搜索结果。

迭代过滤(Iterative Filtering)

标准过滤能够有效缩小搜索范围。

但是,当过滤表达式(Filtering Expression)非常复杂时,执行元数据过滤本身可能会带来较高的搜索延迟(Latency)。

在这种情况下,可以采用迭代过滤(Iterative Filtering),以降低标量过滤(Scalar Filtering)的计算开销。

其工作流程如下图所示:

如上图所示,迭代过滤采用**逐轮(Iteration)**执行搜索的方式:

  1. 首先执行向量搜索,返回一个候选实体。
  2. 对该实体执行标量过滤(Scalar Filtering)。
  3. 如果满足过滤条件,则保留该结果。
  4. 如果未达到指定的 Top-K 数量,则继续获取下一个候选实体。
  5. 重复上述过程,直到收集到足够数量的结果。

这种方式可以显著减少需要执行标量过滤的实体数量,因此特别适用于过滤表达式非常复杂的场景。

不过需要注意的是,由于迭代器一次只处理一个实体,这种串行处理方式可能导致:

  • 搜索耗时增加;
  • 当需要过滤大量实体时,整体性能可能下降。

本节演示如何执行过滤搜索(Filtered Search),假设你的 Collection 中已经包含如下数据。每个实体(Entity)都包含四个字段:

  • id
  • vector
  • color
  • likes
json 复制代码
[
    {"id": 0, "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], "color": "pink_8682", "likes": 165},
    {"id": 1, "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104], "color": "red_7025", "likes": 25},
    {"id": 2, "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592], "color": "orange_6781", "likes": 764},
    {"id": 3, "vector": [0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345], "color": "pink_9298", "likes": 234},
    {"id": 4, "vector": [0.4452349528804562, -0.8757026943054742, 0.8220779437047674, 0.46406290649483184, 0.30337481143159106], "color": "red_4794", "likes": 122},
    {"id": 5, "vector": [0.985825131989184, -0.8144651566660419, 0.6299267002202009, 0.1206906911183383, -0.1446277761879955], "color": "yellow_4222", "likes": 12},
    {"id": 6, "vector": [0.8371977790571115, -0.015764369584852833, -0.31062937026679327, -0.562666951622192, -0.8984947637863987], "color": "red_9392", "likes": 58},
    {"id": 7, "vector": [-0.33445148015177995, -0.2567135004164067, 0.8987539745369246, 0.9402995886420709, 0.5378064918413052], "color": "grey_8510", "likes": 775},
    {"id": 8, "vector": [0.39524717779832685, 0.4000257286739164, -0.5890507376891594, -0.8650502298996872, -0.6140360785406336], "color": "white_9381", "likes": 876},
    {"id": 9, "vector": [0.5718280481994695, 0.24070317428066512, -0.3737913482606834, -0.06726932177492717, -0.6980531615588608], "color": "purple_4976", "likes": 765}
]

如果查询向量本身已经存在于目标 Collection 中,建议直接使用 ids 参数,而不是先查询向量再进行搜索。

下面的示例演示如何使用标准过滤(Standard Filtering)

搜索请求中同时包含:

  • 一个过滤条件(filter
  • 多个输出字段(output_fields
python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

query_vector = [
    0.3580376395471989,
    -0.6023495712049978,
    0.18414012509913835,
    -0.26286205330961354,
    0.9029438446296592
]

res = client.search(
    collection_name="my_collection",
    data=[query_vector],
    limit=5,
    filter='color like "red%" and likes > 50',
    output_fields=["color", "likes"]
)

for hits in res:
    print("TopK results:")
    for hit in hits:
        print(hit)

上述搜索请求中的过滤条件为:

sql 复制代码
color LIKE "red%" AND likes > 50

该过滤表达式通过 and 运算符组合了两个条件:

  1. color LIKE "red%"

    要求 color 字段的值以 red 开头,例如:

    • red_4794
    • red_9392
  2. likes > 50

    要求 likes 字段的值大于 50

根据示例数据,只有两个实体满足上述条件。

因此,即使 top-K 设置为 5(原文说明写为 3,应为文档笔误),Milvus 也只会计算这两个实体与查询向量之间的距离,并将它们作为最终搜索结果返回。

返回结果如下:

json 复制代码
[
    {
        "id": 4,
        "distance": 0.3345786594834839,
        "entity": {
            "vector": [
                0.4452349528804562,
                -0.8757026943054742,
                0.8220779437047674,
                0.46406290649483184,
                0.30337481143159106
            ],
            "color": "red_4794",
            "likes": 122
        }
    },
    {
        "id": 6,
        "distance": 0.6638239834383389,
        "entity": {
            "vector": [
                0.8371977790571115,
                -0.015764369584852833,
                -0.31062937026679327,
                -0.562666951622192,
                -0.8984947637863987
            ],
            "color": "red_9392",
            "likes": 58
        }
    }
]

有关元数据过滤支持的所有运算符,请参阅 Filtering

如果希望采用**迭代过滤(Iterative Filtering)**方式,可以在 search_params 中指定:

python 复制代码
"hints": "iterative_filter"

完整示例如下:

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

query_vector = [
    0.3580376395471989,
    -0.6023495712049978,
    0.18414012509913835,
    -0.26286205330961354,
    0.9029438446296592
]

res = client.search(
    collection_name="my_collection",
    data=[query_vector],
    limit=5,
    filter='color like "red%" and likes > 50',
    output_fields=["color", "likes"],
    search_params={
        "hints": "iterative_filter"
    }
)

for hits in res:
    print("TopK results:")
    for hit in hits:
        print(hit)

该示例与标准过滤的唯一区别在于,通过设置:

python 复制代码
search_params = {
    "hints": "iterative_filter"
}

告诉 Milvus 使用迭代过滤模式执行搜索,而不是默认的标准过滤模式。

范围搜索(Range Search)

范围搜索通过将返回结果的距离或得分限制在一个指定区间内,从而提升搜索结果的相关性。

在执行范围搜索请求时,Milvus 的处理方式如下:

  1. 从 ANN(Approximate Nearest Neighbor,近似最近邻)搜索结果中找到与查询向量最相似的向量
  2. 以该最相似向量作为圆心;
  3. radius 作为外圆半径
  4. range_filter 作为内圆半径
  5. 构建两个同心圆;
  6. 返回所有落在两个圆之间**环形区域(annular region)**中的向量。

其中:

  • range_filter 可以设置为 0,表示返回所有在 radius 范围内的结果。

如图所示,一个范围搜索请求包含两个核心参数:

  • radius
  • range_filter

Milvus 接收到请求后执行如下流程:

  1. 使用指定的度量方式(Metric Type,例如 COSINE)计算与查询向量最相似的向量;
  2. 根据 radiusrange_filter 对结果进行区间过滤;
  3. 返回过滤后结果中的 Top-K 实体。

radiusrange_filter 的具体设置方式取决于不同的距离/相似度度量方式(Metric Type)

Metric Type 含义说明 radius 与 range_filter 的设置规则(用于排除最相似向量)
L2 L2 距离越小表示越相似 range_filter <= distance < radius
IP 内积(IP)越大表示越相似 radius < distance <= range_filter
COSINE 余弦相似度越大表示越相似 radius < distance <= range_filter
JACCARD Jaccard 距离越小表示越相似 range_filter <= distance < radius
HAMMING Hamming 距离越小表示越相似 range_filter <= distance < radius

说明

  • 对于"距离越小越相似"的度量(如 L2 / JACCARD / HAMMING),范围通常写成:

    text 复制代码
    range_filter <= distance < radius
  • 对于"值越大越相似"的度量(如 IP / COSINE),范围通常写成:

    text 复制代码
    radius < distance <= range_filter

以下代码示例中的搜索请求未显式指定 metric type(度量类型) ,因此默认使用 COSINE(余弦相似度)

在这种情况下,需要确保:

  • radius < range_filter

在下面的示例中:

  • radius = 0.4
  • range_filter = 0.6

因此 Milvus 会返回所有与查询向量之间的距离(或相似度得分)落在 0.4 到 0.6 区间内的实体。

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

query_vector = [
    0.3580376395471989,
    -0.6023495712049978,
    0.18414012509913835,
    -0.26286205330961354,
    0.9029438446296592
]

res = client.search(
    collection_name="my_collection",
    data=[query_vector],
    limit=3,
    search_params={
        "params": {
            "radius": 0.4,
            "range_filter": 0.6
        }
    }
)

for hits in res:
    print("TopK results:")
    for hit in hits:
        print(hit)

分组搜索(Grouping Search)

分组搜索允许 Milvus 根据指定字段的值对搜索结果进行分组,从而在更高层级上对数据进行聚合。

例如:

  • 使用基础 ANN 搜索可以找到与当前书籍相似的书;
  • 但使用分组搜索可以进一步找到可能涉及该书主题的书籍类别

当搜索结果中的多个实体在某个标量字段(scalar field)上具有相同值时,说明它们在某个属性上是相似的。

但这种情况可能会对搜索结果的多样性产生负面影响。

假设一个 Collection 存储了多个文档(docId 表示文档 ID)。

为了在向量化过程中尽可能保留语义信息,每个文档会被拆分成多个更小的段落(paragraph / chunk),并分别作为独立实体存储。

因此:

  • 一个文档会对应多个向量(多个 chunk)
  • 每个 chunk 都是一个独立 entity

但在实际使用中,用户通常更关心的是:

哪些"文档(doc)"最相关,而不是某个单独段落最相似。

在这种数据结构下执行 ANN 搜索时:

  • 搜索结果可能集中在同一个 docId 的多个段落
  • 导致其他文档没有机会进入 Top-K
  • 结果缺乏多样性,不符合实际业务需求

为了提升搜索结果的多样性,可以在搜索请求中添加:

python 复制代码
group_by_field

例如设置:

text 复制代码
group_by_field = docId

当 Milvus 收到分组搜索请求后,会执行以下步骤:

  1. 基于查询向量执行 ANN 搜索,找到最相似的候选实体;
  2. 按照 group_by_field(例如 docId)对结果进行分组;
  3. 在每个分组中,返回最相关的 Top-K 结果(由 limit 控制);
  4. 每个组默认只返回一个最相似的实体。

如果你希望每个分组返回更多结果,可以使用以下参数控制:

  • group_size:每组返回的数量
  • strict_group_size:是否严格限制每组返回数量

以下示例假设 Collection 中包含如下字段:

  • id
  • vector
  • chunk
  • docId
json 复制代码
[
    {"id": 0, "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], "chunk": "pink_8682", "docId": 1},
    {"id": 1, "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104], "chunk": "red_7025", "docId": 5},
    {"id": 2, "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592], "chunk": "orange_6781", "docId": 2},
    {"id": 3, "vector": [0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345], "chunk": "pink_9298", "docId": 3},
    {"id": 4, "vector": [0.4452349528804562, -0.8757026943054742, 0.8220779437047674, 0.46406290649483184, 0.30337481143159106], "chunk": "red_4794", "docId": 3},
    {"id": 5, "vector": [0.985825131989184, -0.8144651566660419, 0.6299267002202009, 0.1206906911183383, -0.1446277761879955], "chunk": "yellow_4222", "docId": 4},
    {"id": 6, "vector": [0.8371977790571115, -0.015764369584852833, -0.31062937026679327, -0.562666951622192, -0.8984947637863987], "chunk": "red_9392", "docId": 1},
    {"id": 7, "vector": [-0.33445148015177995, -0.2567135004164067, 0.8987539745369246, 0.9402995886420709, 0.5378064918413052], "chunk": "grey_8510", "docId": 2},
    {"id": 8, "vector": [0.39524717779832685, 0.4000257286739164, -0.5890507376891594, -0.8650502298996872, -0.6140360785406336], "chunk": "white_9381", "docId": 5},
    {"id": 9, "vector": [0.5718280481994695, 0.24070317428066512, -0.3737913482606834, -0.06726932177492717, -0.6980531615588608], "chunk": "purple_4976", "docId": 3}
]

在搜索请求中,同时设置:

  • group_by_field = docId
  • output_fields = ["docId"]

Milvus 会按照 docId 对结果进行分组,并从每个分组中返回最相似的实体,同时返回对应的 docId 字段值。

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

query_vectors = [
    [0.14529211512077012, 0.9147257273453546, 0.7965055218724449, 0.7009258593102812, 0.5605206522382088]
]

# 分组搜索
res = client.search(
    collection_name="my_collection",
    data=query_vectors,
    limit=3,
    group_by_field="docId",
    output_fields=["docId"]
)

# 获取返回结果中的 docId
doc_ids = [result['entity']['docId'] for result in res[0]]

上述请求中:

  • limit=3 表示返回 3 个分组
  • 每个分组默认只返回 1 条最相似实体

默认情况下,分组搜索每个 group 只返回一个结果。

如果希望每组返回多个结果,可以通过以下参数控制:

  • group_size
  • strict_group_size
python 复制代码
res = client.search(
    collection_name="my_collection",
    data=query_vectors,
    limit=5,
    group_by_field="docId",
    group_size=2,
    strict_group_size=True,
    output_fields=["docId"]
)

参数说明

group_size:用于指定每个分组返回的实体数量

例如:

  • group_size=2 表示每个 docId 返回 2 个最相似的 chunk。

如果未设置,默认每组返回 1 条结果。

strict_group_size:控制是否严格执行 group_size 数量要求

  • strict_group_size=True

    尽量保证每个分组返回 group_size 个实体(如果该组数据不足则例外)

  • strict_group_size=False(默认)

    优先保证 limit 指定的分组数量,而不是严格满足每组的返回数量

这种模式在数据分布不均时性能更好。

按标量字段排序分组(Order Groups by Scalar Field)

可以将分组搜索与 order_by_fields 结合使用,对分组结果进行排序,例如按价格或评分排序。

示例:

python 复制代码
res = client.search(
    collection_name="product_catalog",
    data=query_vectors,
    anns_field="embedding",
    limit=20,
    group_by_field="category",
    group_size=3,
    strict_group_size=True,
    output_fields=["category", "price", "rating"],
    order_by_fields=[
        {"field": "price", "order": "asc"}
    ],
)

说明

  • limit=20 表示返回 最多 20 个分组
  • 不是 20 个实体
  • 每组最多 group_size=3,因此最多返回 60 个实体

当同时使用:

  • group_by_field
  • order_by_fields

Milvus 会:

  • 按每个 group 中最优实体的标量字段值对 group 进行排序
  • 在每个 group 内部,仍然按照相似度得分排序

注意事项(Considerations)

  1. 索引要求

    分组搜索仅支持以下索引类型:

    • FLAT
    • IVF_FLAT
    • IVF_SQ8
    • HNSW
    • HNSW_PQ
    • HNSW_PRQ
    • HNSW_SQ
    • DISKANN
    • SPARSE_INVERTED_INDEX
  2. 分组数量(limit)

    • limit 控制的是分组数量
    • 不是实体数量
    • 合理设置 limit 可以提升多样性和性能
  3. 每组返回数量(group_size)

    • 控制每个 group 返回多少实体
    • 数据分布不均时,某些 group 可能返回不足
  4. strict_group_size

    • True:尽量保证每组返回固定数量
    • False:优先保证整体性能与 group 数量

主键搜索(Primary-Key Search)

在进行相似性搜索时,通常需要提供一个或多个查询向量(Query Vectors),即使这些向量已经存在于目标 Collection 中。

为了避免在搜索前再次检索向量数据,可以直接使用**主键(Primary Keys)**进行搜索。

在电商平台中,用户通常会通过关键词搜索商品。

例如:

  • 用户输入关键词后,系统返回匹配的商品列表;
  • 当用户进入某个商品详情页时,系统还会在页面底部推荐"相似商品"。

这些推荐结果通常按照与关键词或当前商品的相似度进行排序。

为了实现上述功能,开发者通常需要:

  1. 从 Milvus 中先查询关键词或当前商品对应的向量表示;
  2. 再使用该向量执行相似性搜索;
  3. 最终返回结果。

这种方式会带来一些问题:

  • 增加一次额外的网络往返(round-trip);
  • 需要在网络中传输大量高维浮点向量数据;
  • 增加系统开销与延迟。

为了简化应用与 Milvus 的交互逻辑,并减少网络传输开销,可以使用主键搜索(Primary-Key Search)

在主键搜索中:

  • 不再需要提供查询向量;

  • 只需提供包含查询向量的实体的 主键(ids)

  • 主键搜索适用于所有向量类型

    • 但不包括:由 VarChar 字段通过 BM25 生成的稀疏向量字段
  • 支持在以下场景中使用主键替代查询向量:

    • 过滤搜索(Filtered Search)
    • 范围搜索(Range Search)
    • 分组搜索(Grouping Search)
    • 支持分页(Pagination)
  • 不支持场景:

    • 混合搜索(Hybrid Search)
    • 搜索迭代器(Search Iterator)
    • RESTful API
  • 在 embedding list(嵌入向量列表)场景中:

    • 仍然需要手动获取 query vectors 并构造 embedding list
  • 注意事项:

    • 不存在的主键或格式错误会报错
    • 主键与查询向量互斥,不能同时提供

以下示例默认所有提供的 Int64 类型 ID 都存在于目标 Collection 中。

注意:

主键不是用于过滤条件,而是用于替代查询向量进行检索。

示例 1:基础主键搜索

只需将 query vector 替换为主键列表即可。

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

res = client.search(
    collection_name="quick_setup",
    anns_field="vector",
    ids=[551, 296, 43],  # 主键列表
    limit=3,
    search_params={"metric_type": "IP"}
)

for hits in res:
    for hit in hits:
        print(hit)

示例 2:基于主键的过滤搜索

假设 colorlikes 是 schema 中定义的字段:

python 复制代码
res = client.search(
    collection_name="my_collection",
    ids=[551, 296, 43],
    filter='color like "red%" and likes > 50',
    output_fields=["color", "likes"],
    limit=3,
)

示例 3:主键范围搜索

python 复制代码
res = client.search(
    collection_name="my_collection",
    ids=[551, 296, 43],
    limit=3,
    search_params={
        "params": {
            "radius": 0.4,
            "range_filter": 0.6
        }
    }
)

示例 4:主键分组搜索

假设 docId 是 schema 中的字段:

python 复制代码
res = client.search(
    collection_name="my_collection",
    ids=[551, 296, 43],
    limit=3,
    group_by_field="docId",
    output_fields=["docId"]
)

多向量混合搜索(Multi-Vector Hybrid Search)

在许多应用中,一个对象可以通过丰富的信息进行检索,例如标题与描述,或者通过多种模态(如文本、图像、音频)进行搜索。

例如:

  • 一条推文同时包含文本和图片
  • 只要文本或图片中的任意一种与查询语义匹配,都应该能够被检索出来

混合搜索(Hybrid Search)通过将不同字段上的搜索结果进行组合,从而提升整体搜索体验。

Milvus 支持在多个向量字段上同时进行搜索,从而并行执行多个 ANN(Approximate Nearest Neighbor,近似最近邻)搜索。

当你需要同时搜索文本与图像、同一对象的多个文本字段,或结合稠密向量与稀疏向量提升检索质量时,多向量混合搜索尤其有用。

多向量混合搜索(multi-vector hybrid search)将不同的搜索方式进行整合,或覆盖来自不同模态的 embedding 表示:

稀疏-稠密向量搜索(Sparse-Dense Vector Search)

复制代码
稠密向量(Dense Vector)擅长捕捉语义关系,而稀疏向量(Sparse Vector)在精确关键词匹配方面表现出色。

混合搜索将两者结合起来:

* 提供更广泛的语义理解能力
* 同时保留对精确词项的匹配能力

从而提升整体搜索效果。

通过结合各自的优势,混合搜索可以弥补单一方法的局限性,使复杂查询获得更好的表现。

多模态向量搜索(Multimodal Vector Search)

复制代码
多模态向量搜索是一种强大的技术,允许你在多种数据类型之间进行检索,包括:

* 文本(text)
* 图像(image)
* 音频(audio)
* 其他模态数据

其核心优势在于:

> 将不同模态统一到同一个搜索框架中,提供无缝且一致的检索体验。

例如在商品搜索中:

* 用户输入一段文本查询
* 系统返回同时包含文本描述和图片的商品结果

通过混合多种模态的搜索方式,可以提升搜索准确性,并丰富搜索结果的多样性。

示例(Example)

我们来看一个真实的应用场景:每个商品同时包含文本描述和图片

基于这些数据,我们可以进行三种类型的搜索:

  1. 语义文本搜索(Semantic Text Search)

    通过**稠密向量(dense vector)**对商品的文本描述进行查询。

    文本 embedding 可以由以下模型生成:

    • BERT
    • Transformer 系列模型
    • OpenAI embedding 服务

    该方式的特点是:

    更关注语义相似性,而不是字面匹配。

  2. 全文搜索(Full-Text Search)

    通过**关键词匹配 + 稀疏向量(sparse vector)**对文本进行查询。

    可使用的算法或模型包括:

    • BM25
    • BGE-M3(稀疏 embedding 模型)
    • SPLADE

    该方式的特点是:

    更擅长精确匹配关键词。

  3. 多模态图像搜索(Multimodal Image Search)

    使用文本查询图片内容,通过稠密向量进行匹配。

    图像 embedding 可以由以下模型生成:

    • CLIP

    该方式的特点是:

    支持跨模态检索(文本 → 图像)。

创建包含多个向量字段的 Collection

创建 Collection 的过程包含三个关键步骤:

  1. 定义 Collection Schema
  2. 配置索引参数
  3. 创建 Collection

在多向量混合搜索中,需要在 Collection 的 schema 中定义多个向量字段

在必要情况下,可以通过调整以下参数,将单个 Collection 的向量字段数量扩展到最多 10 个:

text 复制代码
proxy.maxVectorFieldNum

本示例 Schema 包含以下字段:

id

  • 作用:作为主键存储文本 ID
  • 类型:INT64

text

  • 作用:存储文本内容
  • 类型:VARCHAR
  • 最大长度:1000 bytes
  • 参数:enable_analyzer=True(用于支持全文检索)

text_dense

  • 作用:存储文本的稠密向量
  • 类型:FLOAT_VECTOR
  • 维度:768

text_sparse

  • 作用:存储文本的稀疏向量
  • 类型:SPARSE_FLOAT_VECTOR

image_dense

  • 作用:存储商品图像的稠密向量
  • 类型:FLOAT_VECTOR
  • 维度:512

由于需要在 text 字段上使用内置 BM25 算法进行全文搜索,因此必须在 schema 中添加 Milvus Function

python 复制代码
from pymilvus import (
    MilvusClient, DataType, Function, FunctionType
)

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

# 初始化 schema(关闭 auto_id)
schema = client.create_schema(auto_id=False)

# 添加字段
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, description="product id")

schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=1000, enable_analyzer=True, description="raw text of product description")

schema.add_field(field_name="text_dense", datatype=DataType.FLOAT_VECTOR, dim=768, description="text dense embedding")

schema.add_field(field_name="text_sparse", datatype=DataType.SPARSE_FLOAT_VECTOR, description="text sparse embedding auto-generated by BM25")

schema.add_field(field_name="image_dense", datatype=DataType.FLOAT_VECTOR, dim=512, description="image dense embedding")

# 添加 BM25 函数
bm25_function = Function(
    name="text_bm25_emb",
    input_field_names=["text"],
    output_field_names=["text_sparse"],
    function_type=FunctionType.BM25,
)

schema.add_function(bm25_function)

在定义完 Collection schema 之后,下一步是配置向量索引并指定相似度度量方式。在该示例中:

text_dense_index

  • 索引类型:AUTOINDEX
  • 相似度度量:IP(内积)
  • 用于:文本稠密向量字段

text_sparse_index

  • 索引类型:SPARSE_INVERTED_INDEX
  • 相似度度量:BM25
  • 用于:文本稀疏向量字段

image_dense_index

  • 索引类型:AUTOINDEX
  • 相似度度量:IP
  • 用于:图像稠密向量字段

你也可以根据实际需求选择其他索引类型,以适配不同数据和查询场景。

python 复制代码
# 准备索引参数
index_params = client.prepare_index_params()

# text_dense 索引
index_params.add_index(
    field_name="text_dense",
    index_name="text_dense_index",
    index_type="AUTOINDEX",
    metric_type="IP"
)

# text_sparse 索引
index_params.add_index(
    field_name="text_sparse",
    index_name="text_sparse_index",
    index_type="SPARSE_INVERTED_INDEX",
    metric_type="BM25",
    params={"inverted_index_algo": "DAAT_MAXSCORE"},  # 或 DAAT_WAND / TAAT_NAIVE
)

# image_dense 索引
index_params.add_index(
    field_name="image_dense",
    index_name="image_dense_index",
    index_type="AUTOINDEX",
    metric_type="IP"
)

使用前两步中定义的 schema 和索引配置,创建一个名为 demo 的 Collection。

python 复制代码
client.create_collection(
    collection_name="my_collection",
    schema=schema,
    index_params=index_params
)

本节将数据插入到前面定义的 my_collection 中。在插入时,需要确保除了自动生成字段之外,所有字段都按照正确格式提供数据。

在本例中:

id

  • 类型:整数
  • 含义:商品 ID

text

  • 类型:字符串
  • 含义:商品描述文本

text_dense

  • 类型:768 维浮点向量
  • 含义:文本描述的稠密向量 embedding

image_dense

  • 类型:512 维浮点向量
  • 含义:商品图片的稠密向量 embedding

你可以使用相同或不同的模型为不同字段生成 embedding。

在本例中:

两个 dense 向量维度不同,说明它们由不同模型生成。

因此在后续搜索时,需要确保使用对应模型生成查询向量。

由于本例使用内置 BM25 函数从 text 字段自动生成稀疏向量,因此不需要手动提供 sparse vector

但如果不使用 BM25,则必须自行预先计算并提供稀疏向量。

python 复制代码
import random

# 生成示例向量
def generate_dense_vector(dim):
    return [random.random() for _ in range(dim)]

data = [
    {
        "id": 0,
        "text": "红色纯棉圆领 T 恤",
        "text_dense": generate_dense_vector(768),
        "image_dense": generate_dense_vector(512)
    },
    {
        "id": 1,
        "text": "无线降噪头戴式耳机",
        "text_dense": generate_dense_vector(768),
        "image_dense": generate_dense_vector(512)
    },
    {
        "id": 2,
        "text": "不锈钢保温水瓶,500ml",
        "text_dense": generate_dense_vector(768),
        "image_dense": generate_dense_vector(512)
    }
]

res = client.insert(
    collection_name="my_collection",
    data=data
)

执行混合搜索(Perform Hybrid Search)

混合搜索(Hybrid Search)是通过在 hybrid_search() 函数中创建多个 AnnSearchRequest 来实现的,其中每个 AnnSearchRequest 都代表一个针对特定向量字段的基础 ANN(Approximate Nearest Neighbor)搜索请求。

因此,在执行混合搜索之前,需要为每个向量字段分别创建一个 AnnSearchRequest

此外,你还可以通过在 AnnSearchRequest 中配置 expr 参数来设置过滤条件,从而实现带过滤的混合搜索。

在混合搜索中,每个 AnnSearchRequest 仅支持一个查询数据(query data)。

为了展示不同向量字段的能力,我们将使用一个示例查询来构造三个 AnnSearchRequest。同时,我们将使用已预计算好的稠密向量(dense vectors)来完成这一过程。

这些搜索请求将分别作用于以下向量字段:

  1. text_dense

    用于语义文本搜索(semantic text search),能够基于"语义理解"进行检索,而不是依赖关键词匹配。

  2. text_sparse

    用于全文搜索或关键词匹配(full-text / keyword matching),重点关注文本中的精确词或短语匹配。

  3. image_dense

    用于多模态文本到图像检索(text-to-image search),根据查询语义检索相关的商品图片。

为了展示不同向量字段的能力,这里构造三个 AnnSearchRequest

python 复制代码
from pymilvus import AnnSearchRequest

query_text = "white headphones, quiet and comfortable"
query_dense_vector = generate_dense_vector(768)
query_multimodal_vector = generate_dense_vector(512)

用于基于语义的文本检索(不依赖关键词匹配)

python 复制代码
search_param_1 = {
    "data": [query_dense_vector],
    "anns_field": "text_dense",
    "param": {"nprobe": 10},
    "limit": 2
}

request_1 = AnnSearchRequest(**search_param_1)

用于关键词匹配或全文搜索

python 复制代码
search_param_2 = {
    "data": [query_text],
    "anns_field": "text_sparse",
    "limit": 2
}

request_2 = AnnSearchRequest(**search_param_2)

用于文本检索图片内容

python 复制代码
search_param_3 = {
    "data": [query_multimodal_vector],
    "anns_field": "image_dense",
    "param": {"nprobe": 10},
    "limit": 2
}

request_3 = AnnSearchRequest(**search_param_3)

合并请求:

python 复制代码
reqs = [request_1, request_2, request_3]

由于每个 limit=2

  • 每个 AnnSearchRequest 返回 2 条结果
  • 总共 3 个请求
  • 因此总结果数为 6 条

为了合并并重新排序多个 ANN 结果,需要使用 reranking 策略。

Milvus 支持多种 reranker:

  • Weighted Ranker
  • RRF Ranker(Reciprocal Rank Fusion)

在本例中,由于没有对某一类搜索特别加权,因此使用 RRF Ranker

python 复制代码
ranker = Function(
    name="rrf",
    input_field_names=[],  # 必须为空列表
    function_type=FunctionType.RERANK,
    params={
        "reranker": "rrf",
        "k": 100  # 可选参数
    }
)

在执行 hybrid_search 之前,需要确保:

  • Collection 已加载(loaded)
  • 所有向量字段已建立索引并加载到内存

否则会报错。

python 复制代码
res = client.hybrid_search(
    collection_name="my_collection",
    reqs=reqs,
    ranker=ranker,
    limit=2
)

for hits in res:
    print("TopK results:")
    for hit in hits:
        print(hit)

输出示例

text 复制代码
['id: 1, distance: 0.006047376897186041, entity: {}',
 'id: 2, distance: 0.006422005593776703, entity: {}']

由于 limit=2

  • Milvus 会将 3 个搜索请求得到的 6 个结果
  • 通过 RRF rerank 进行融合排序
  • 最终只返回 最相关的前 2 个结果

高级用法(Advanced usage)------在混合搜索中临时设置时区

如果你的 collection 中包含 TIMESTAMPTZ 字段,可以通过在 hybrid_search 请求中设置 timezone 参数,在单次操作中临时覆盖数据库或 collection 的默认时区。

该设置用于控制:

在本次操作中,TIMESTAMPTZ 字段的显示方式以及比较规则。

timezone 的值必须是一个合法的 IANA 时区标识符,例如:

  • Asia/Shanghai
  • America/Chicago
  • UTC

示例:在混合搜索中设置时区

python 复制代码
res = client.hybrid_search(
    collection_name="my_collection",
    reqs=reqs,
    ranker=ranker,
    limit=2,
    timezone="America/Havana",
)

Query 查询

除了 ANN(近似最近邻)搜索之外,Milvus 还支持通过 Query(查询) 进行元数据过滤。

如果你在 Collection 创建后动态新增字段:

查询这些字段时,对于未显式赋值的实体,将返回默认值或 NULL

一个 Collection 可以存储多种类型的标量字段(scalar fields)。Milvus 支持基于一个或多个标量字段对实体进行过滤。Milvus 提供三种查询方式:

  • Query
  • Get
  • QueryIterator
类型 Get Query QueryIterator
适用场景 根据主键查询指定实体 查询满足条件的全部或部分实体 分页查询满足条件的全部实体
过滤方式 通过主键 通过过滤表达式 通过过滤表达式
必填参数 Collection 名称 + 主键 Collection 名称 + 过滤表达式 Collection 名称 + 过滤表达式
可选参数 Partition 名称、输出字段 Partition 名称、返回数量、输出字段 Partition 名称、每页返回数量、输出字段
返回结果 返回指定主键对应的实体 返回满足条件的全部或部分实体 分页返回满足条件的所有实体
  • Get:用于精准查找某些 ID 对应的实体
  • Query:用于一次性查询符合条件的结果
  • QueryIterator:用于分页查询大规模结果集

返回结果说明

  • Get:返回指定主键对应的实体(可限定 partition)
  • Query:返回满足条件的全部或部分实体(可限制数量)
  • QueryIterator:通过分页方式返回所有匹配实体

使用 Get(Use Get)

当你需要通过主键(primary keys)查找实体 时,可以使用 Get 方法。

以下示例假设你的 collection 中包含三个字段:

  • id
  • vector
  • color

示例数据

json 复制代码
[
    {"id": 0, "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], "color": "pink_8682"},
    {"id": 1, "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104], "color": "red_7025"},
    {"id": 2, "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592], "color": "orange_6781"},
    {"id": 3, "vector": [0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345], "color": "pink_9298"},
    {"id": 4, "vector": [0.4452349528804562, -0.8757026943054742, 0.8220779437047674, 0.46406290649483184, 0.30337481143159106], "color": "red_4794"},
    {"id": 5, "vector": [0.985825131989184, -0.8144651566660419, 0.6299267002202009, 0.1206906911183383, -0.1446277761879955], "color": "yellow_4222"},
    {"id": 6, "vector": [0.8371977790571115, -0.015764369584852833, -0.31062937026679327, -0.562666951622192, -0.8984947637863987], "color": "red_9392"},
    {"id": 7, "vector": [-0.33445148015177995, -0.2567135004164067, 0.8987539745369246, 0.9402995886420709, 0.5378064918413052], "color": "grey_8510"},
    {"id": 8, "vector": [0.39524717779832685, 0.4000257286739164, -0.5890507376891594, -0.8650502298996872, -0.6140360785406336], "color": "white_9381"},
    {"id": 9, "vector": [0.5718280481994695, 0.24070317428066512, -0.3737913482606834, -0.06726932177492717, -0.6980531615588608], "color": "purple_4976"}
]

你可以按如下方式根据 ID 获取实体:

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

res = client.get(
    collection_name="my_collection",
    ids=[0, 1, 2],
    output_fields=["vector", "color"]
)

print(res)

使用 Query(Use Query)

基础查询(Basic Query)

当你需要通过自定义过滤条件 查找实体时,可以使用 Query 方法。

以下示例假设 collection 中包含三个字段:

  • id
  • vector
  • color

并返回所有 colorred 开头的指定数量实体。

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

res = client.query(
    collection_name="my_collection",
    filter="color like \"red%\"",
    output_fields=["vector", "color"],
    limit=3
)

查询结果排序(Sort Query Results)

默认情况下,Query 返回结果的顺序是不确定的。可以使用 order_by 参数对结果按一个或多个标量字段进行排序。

使用 order_by 时的注意事项

  • 必须与 limit 一起使用

  • 支持字段类型:

    • INT8 / INT16 / INT32 / INT64
    • FLOAT / DOUBLE
    • VARCHAR
  • 不支持排序的字段类型:

    • vector
    • JSON
    • ARRAY
  • 对于可空字段(nullable field):

    • 升序:NULL 排在最后(NULLS LAST)
    • 降序:NULL 排在最前(NULLS FIRST)

基础排序(Basic Sort)使用 order_by 时,传入格式如下:

text 复制代码
"field_name:direction"

其中:

  • asc:升序(ascending)
  • desc:降序(descending)
  • 注意:大小写敏感
python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

# 按 id 升序排序
res = client.query(
    collection_name="my_collection",
    filter="color like \"red%\"",
    output_fields=["vector", "color"],
    limit=3,
    order_by=["id:asc"],
)

**多字段排序(Multi-field Sort)**可以同时按多个字段排序:

  • 首先按第一个字段排序
  • 若相同,则按第二个字段排序
  • 以此类推
python 复制代码
# 先按 rating 降序,再按 price 升序
res = client.query(
    collection_name="my_collection",
    filter="",
    output_fields=["color", "rating", "price"],
    limit=10,
    order_by=["rating:desc", "price:asc"],
)

排序 + 分页(Pagination with Sort)

可以结合 order_by + limit + offset 实现分页查询。

例如:按价格排序分页展示商品列表,每页返回一批数据,保证排序连续且无重复或遗漏。

python 复制代码
# 第 1 页
page1 = client.query(
    collection_name="my_collection",
    filter="color like \"red%\"",
    output_fields=["color", "price"],
    limit=5,
    offset=0,
    order_by=["price:asc"],
)

# 第 2 页
page2 = client.query(
    collection_name="my_collection",
    filter="color like \"red%\"",
    output_fields=["color", "price"],
    limit=5,
    offset=5,
    order_by=["price:asc"],
)

聚合查询结果(Aggregate Query Results):

你可以按照一个或多个标量字段对查询结果进行分组,并在每个分组上执行聚合计算。

支持的聚合操作包括:

  • count
  • min
  • max
  • sum
  • avg

使用 group_by_fields 时需注意:

  1. 支持分组的字段类型,可以用于分组的字段类型包括:

    • INT8
    • INT16
    • INT32
    • INT64
    • VARCHAR
    • TIMESTAMPTZ

    ❌ 不支持作为分组字段的类型:

    • FLOAT
    • DOUBLE
    • vector
    • JSON
    • ARRAY

    否则会返回错误。

  2. sum / avg 的限制

    • sumavg 只能用于数值类型字段

    • 支持:

      • INT / FLOAT / DOUBLE 等数值字段
    • ❌ 如果用于 VARCHAR 等非数值字段会报错

  3. 如何启用聚合

    query() 中:

    • 使用 group_by_fields 指定分组字段
    • output_fields 中写入聚合表达式,例如:
    text 复制代码
    count(*)
    count(<field>)
    min(<field>)
    max(<field>)
    sum(<field>)
    avg(<field>)

下面示例按 color 分组,并统计每个颜色的实体数量:

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

res = client.query(
    collection_name="my_collection",
    filter="",
    group_by_fields=["color"],
    output_fields=["color", "count(*)"],
)

输出示例:

python 复制代码
[
 {'color': 'red',    'count(*)': 10},
 {'color': 'orange', 'count(*)': 10},
 {'color': 'yellow', 'count(*)': 10},
 {'color': 'green',  'count(*)': 10},
 {'color': 'blue',   'count(*)': 10}
]

你可以在一次调用中请求多个聚合表达式。下面的示例按 color(颜色)分组,并返回每个分组的:

  • 实体数量(count)
  • 平均价格(average price)
  • 最高评分(maximum rating)
python 复制代码
res = client.query(
    collection_name="my_collection",
    filter="",
    group_by_fields=["color"],
    output_fields=["color", "count(*)", "avg(price)", "max(rating)"],
)

输出示例:

python 复制代码
[
 {'color': 'red',    'count(*)': 10, 'avg(price)': 65.22, 'max(rating)': 5},
 {'color': 'orange', 'count(*)': 10, 'avg(price)': 48.67, 'max(rating)': 5},
 {'color': 'yellow', 'count(*)': 10, 'avg(price)': 64.15, 'max(rating)': 3},
 {'color': 'green',  'count(*)': 10, 'avg(price)': 58.28, 'max(rating)': 5},
 {'color': 'blue',   'count(*)': 10, 'avg(price)': 50.20, 'max(rating)': 5}
]

group_by_fields 传入多个字段,可以计算复合分组(composite groups)。下面的示例按(color,rating)进行分组,并计算每个分组中的价格区间(price range):

python 复制代码
res = client.query(
    collection_name="my_collection",
    filter="",
    group_by_fields=["color", "rating"],
    output_fields=["color", "rating", "min(price)", "max(price)"],
)

输出示例:

python 复制代码
[
 {'color': 'red',    'rating': 5, 'min(price)': 34.51, 'max(price)': 70.90},
 {'color': 'orange', 'rating': 2, 'min(price)': 12.39, 'max(price)': 81.99},
 {'color': 'yellow', 'rating': 2, 'min(price)': 22.62, 'max(price)': 88.24},
 {'color': 'green',  'rating': 1, 'min(price)': 18.35, 'max(price)': 59.53},
 {'color': 'blue',   'rating': 4, 'min(price)': 21.23, 'max(price)': 82.45},
 ...
]

你也可以将 group_by_fieldslimit 结合使用,以限制返回的分组数量。当某个字段的基数(cardinality)很高,而你只需要获取部分分组样本时,这种方式非常有用:

python 复制代码
res = client.query(
    collection_name="my_collection",
    filter="",
    group_by_fields=["color"],
    output_fields=["color", "avg(price)", "count(*)"],
    limit=5,
)

输出示例:

python 复制代码
[
 {'color': 'red',    'avg(price)': 65.22, 'count(*)': 10},
 {'color': 'orange', 'avg(price)': 48.67, 'count(*)': 10},
 {'color': 'yellow', 'avg(price)': 64.15, 'count(*)': 10},
 {'color': 'green',  'avg(price)': 58.28, 'count(*)': 10},
 {'color': 'blue',   'avg(price)': 50.20, 'count(*)': 10}
]

使用 QueryIterator

当你需要通过自定义过滤条件进行分页查询时,可以创建 QueryIterator,并使用其 next() 方法逐批遍历所有符合条件的实体。下面的示例假设集合中包含 idvectorcolor 三个字段,并返回所有 color 值以 red 开头的实体。

python 复制代码
iterator = client.query_iterator(
    "my_collection",
    batch_size=10,
    filter="color like \"red%\"",
    output_fields=["color"]
)

results = []

while True:
    result = iterator.next()
    if not result:
        iterator.close()
        break

    print(result)
    results += result

你也可以通过在 GetQueryQueryIterator 请求中指定分区名称,在一个或多个分区内进行查询。以下示例假设集合中存在名为 PartitionA 的分区。

python 复制代码
res = client.get(
    collection_name="my_collection",
    partitionNames=["partitionA"],
    ids=[10, 11, 12],
    output_fields=["vector", "color"]
)

res = client.query(
    collection_name="my_collection",
    partitionNames=["partitionA"],
    filter="color like \"red%\"",
    output_fields=["vector", "color"],
    limit=3
)

使用 QueryIterator:

python 复制代码
iterator = client.query_iterator(
    "my_collection",
    partition_names=["partitionA"],
    batch_size=10,
    filter="color like \"red%\"",
    output_fields=["color"]
)

results = []
while True:
    result = iterator.next()
    if not result:
        iterator.close()
        break

    print(result)
    results += result

如果你想从集合中抽取一部分代表性数据,用于数据探索或开发测试,可以使用 RANDOM_SAMPLE(sampling_factor) 表达式,其中 sampling_factor 是介于 0 和 1 之间的浮点数,表示采样比例。

python 复制代码
# 从整个集合中采样 1%
res = client.query(
    collection_name="my_collection",
    filter="RANDOM_SAMPLE(0.01)",
    output_fields=["vector", "color"]
)

print(f"从集合中采样得到 {len(res)} 个实体")

# 与其他过滤条件结合使用 ------ 先过滤,再采样
res = client.query(
    collection_name="my_collection", 
    filter="color like \"red%\" AND RANDOM_SAMPLE(0.005)",
    output_fields=["vector", "color"],
    limit=10
)

print(f"在采样中找到 {len(res)} 个 red 数据")

如果你的集合中包含 TIMESTAMPTZ 字段,可以通过在 query 调用中设置 timezone 参数,临时覆盖数据库或集合的默认时区。该参数会影响 TIMESTAMPTZ 字段在查询过程中的显示与比较方式。

timezone 必须是合法的 IANA 时区标识(例如 Asia/ShanghaiAmerica/ChicagoUTC)。更多信息可参考 TIMESTAMPTZ Field。

下面示例展示如何在查询中临时设置时区:

python 复制代码
# 查询数据,并将 tsz 字段按 America/Havana 时区显示
results = client.query(
    "my_collection",
    filter="id <= 10",
    output_fields=["id", "tsz", "vec"],
    limit=2,
    timezone="America/Havana",
)

标量过滤规则

谓词表达式(predicate expression)会输出一个布尔值。Milvus 通过谓词进行标量过滤。一个谓词表达式在被求值后,只会返回 TRUE 或 FALSE。

EBNF(扩展巴科斯-瑙尔范式)语法规则如下,用于描述布尔表达式:

ebnf 复制代码
Expr = LogicalExpr | NIL

LogicalExpr =
    LogicalExpr BinaryLogicalOp LogicalExpr
    | UnaryLogicalOp LogicalExpr
    | "(" LogicalExpr ")"
    | SingleExpr;

BinaryLogicalOp = "&&" | "and" | "||" | "or";
UnaryLogicalOp = "not";

SingleExpr = TermExpr | CompareExpr;

TermExpr = IDENTIFIER "in" ConstantArray;

Constant = INTEGER | FLOAT;

ConstantExpr =
    Constant
    | ConstantExpr BinaryArithOp ConstantExpr
    | UnaryArithOp ConstantExpr;

ConstantArray = "[" ConstantExpr { "," ConstantExpr } "]";

UnaryArithOp = "+" | "-";
BinaryArithOp = "+" | "-" | "*" | "/" | "%" | "**";

CompareExpr =
    IDENTIFIER CmpOp IDENTIFIER
    | IDENTIFIER CmpOp ConstantExpr
    | ConstantExpr CmpOp IDENTIFIER
    | ConstantExpr CmpOpRestricted IDENTIFIER CmpOpRestricted ConstantExpr;

CmpOpRestricted = "<" | "<=";
CmpOp = ">" | ">=" | "<" | "<=" | "==" | "!=";

MatchOp = "like" | "LIKE";

JsonArrayOps =
    JsonDefs "(" IDENTIFIER "," JsonExpr | JsonArray ")";

JsonArrayDefs =
    "json_contains" | "JSON_CONTAINS"
    | "json_contains_all" | "JSON_CONTAINS_ALL"
    | "json_contains_any" | "JSON_CONTAINS_ANY";

JsonExpr = Constant | ConstantArray | STRING | BOOLEAN;

JsonArray = "[" JsonExpr { "," JsonExpr } "]";

ArrayOps =
    ArrayDefs "(" IDENTIFIER "," ArrayExpr | Array ")";

ArrayDefs =
    "array_contains" | "ARRAY_CONTAINS"
    | "array_contains_all" | "ARRAY_CONTAINS_ALL"
    | "array_contains_any" | "ARRAY_CONTAINS_ANY"
    | "array_length" | "ARRAY_LENGTH";

ArrayExpr = Constant | ConstantArray | STRING | BOOLEAN;

Array = "[" ArrayExpr { "," ArrayExpr } "]";

下表列出了上述布尔表达式规则中涉及的每个符号的说明。

符号 说明
= 定义
, 连接
; 终止
{...} 重复
(...) 分组
NIL 空值。表达式可以为空字符串
INTEGER 整数,例如 1、2、3
FLOAT 浮点数,例如 1.0、2.0
CONST 整数或浮点数
IDENTIFIER 标识符。在 Milvus 中表示字段名
LogicalOp 逻辑运算符,用于在一次比较中组合多个关系操作。LogicalOp 的返回值为 TRUE(1) 或 FALSE(0)。LogicalOp 分为二元逻辑运算和一元逻辑运算
UnaryLogicalOp 一元逻辑运算符,指 not
BinaryLogicalOp 二元逻辑运算符,对两个操作数执行逻辑运算。在包含多个操作数的复杂表达式中,求值顺序取决于优先级规则
ArithmeticOp 算术运算符,对操作数执行数学运算,例如加减
UnaryArithOp 一元算术运算符,对单个操作数进行运算。负号一元运算可将正值变为负值,反之亦然
BinaryArithOp 二元算术运算符,对两个操作数进行运算。在复杂表达式中,求值顺序取决于优先级规则
CmpOp 比较运算符,对两个操作数进行关系比较
CmpOpRestricted 受限比较运算符,仅限"小于(<)"和"等于(<=)"
ConstantExpr 常量表达式,可以是常量,或由两个 ConstantExpr 通过二元算术运算构成,或由一元算术运算构成。该定义是递归的
ConstantArray 常量数组,由方括号包裹,且可以重复 ConstantExpr。必须至少包含一个 ConstantExpr
TermExpr 用于判断某个 IDENTIFIER 的值是否出现在 ConstantArray 中,通过 in 表示
CompareExpr 比较表达式,可以是:两个 IDENTIFIER 之间的比较,或 IDENTIFIER 与 ConstantExpr 的比较,或 ConstantExpr 与 IDENTIFIER 的比较,或两个 ConstantExpr 与一个 IDENTIFIER 的三元比较
SingleExpr 单表达式,可以是 TermExpr 或 CompareExpr
LogicalExpr 逻辑表达式,可以是:两个 LogicalExpr 通过 BinaryLogicalOp 连接,或 UnaryLogicalOp 作用于 LogicalExpr,或括号包裹的 LogicalExpr,或 SingleExpr。该定义是递归的
Expr 表达式的简称,可以是 LogicalExpr 或 NIL
MatchOp 匹配运算符,将字符串与字符串常量进行比较,支持前缀、后缀或中缀匹配
JsonArrayOp JSON 操作符,用于检查指定字段是否包含指定元素
ArrayOp 数组操作符,用于检查指定字段是否包含指定元素

运算符(Operators):

逻辑运算符用于对两个表达式进行比较。

符号 操作 示例 说明
and / && expr1 && expr2 当 expr1 和 expr2 都为 true 时返回 true
or / || expr1 || expr2 当 expr1 或 expr2 任意一个为 true 时返回 true

二元算术运算符包含两个操作数,可执行基本算术运算并返回结果。

符号 操作 示例 说明
+ 加法 a + b 两个操作数相加
- 减法 a - b 第一个操作数减去第二个
* 乘法 a * b 两个操作数相乘
/ 除法 a / b 第一个操作数除以第二个
** 幂运算 a ** b 第一个操作数的 b 次方
% 取模 a % b 返回除法后的余数

关系运算符用于判断两个表达式之间的相等、不等或大小规律。

符号 操作 示例 说明
< 小于 a < b 若 a 小于 b 则为 true
> 大于 a > b 若 a 大于 b 则为 true
== 等于 a == b 若 a 等于 b 则为 true
!= 不等于 a != b 若 a 不等于 b 则为 true
<= 小于等于 a <= b 若 a 小于或等于 b 则为 true
>= 大于等于 a >= b 若 a 大于或等于 b 则为 true

下表列出了运算符的优先级与结合性(从上到下优先级递减):

优先级 运算符 类型 结合性
1 + - 一元算术运算符(UnaryArithOp) 从左到右
2 not 一元逻辑运算符(UnaryLogicOp) 从右到左
3 ** 二元算术运算符 从左到右
4 * / % 二元算术运算符 从左到右
5 + - 二元算术运算符 从左到右
6 < <= > >= 比较运算符(CmpOp) 从左到右
7 == != 比较运算符(CmpOp) 从左到右
8 like / LIKE 匹配运算符(MatchOp) 从左到右
9 json_contains / JSON_CONTAINS JSON 数组操作(JsonArrayOp) 从左到右
9 array_contains / ARRAY_CONTAINS 数组操作(ArrayOp) 从左到右
10 json_contains_all / JSON_CONTAINS_ALL JSON 数组操作 从左到右
10 array_contains_all / ARRAY_CONTAINS_ALL 数组操作 从左到右
11 json_contains_any / JSON_CONTAINS_ANY JSON 数组操作 从左到右
11 array_contains_any / ARRAY_CONTAINS_ANY 数组操作 从左到右
12 array_length / ARRAY_LENGTH 数组操作 从左到右
13 && / and 二元逻辑运算符(BinaryLogicOp) 从左到右
14 / or

表达式通常从左到右进行计算。复杂表达式会逐步求值,具体执行顺序由运算符优先级决定。

如果表达式中包含多个相同优先级的运算符,则左侧的运算符优先计算。

例如:

10 / 2 * 5 会被计算为 (10 / 2),然后结果再乘以 5。

当需要让低优先级运算先执行时,必须使用括号:

例如:

30 / 2 + 8 通常会先计算 30 / 2,再加 8。

如果希望先计算 2 + 8,应写成:

30 / (2 + 8)

括号可以嵌套使用,最内层括号优先计算。

Milvus 中所有可用布尔表达式的使用示例如下(其中 int64 表示包含 INT64 类型数据的标量字段,float 表示包含浮点类型数据的标量字段,VARCHAR 表示包含 VARCHAR 类型数据的标量字段):

  1. CmpOp

    复制代码
    "int64 > 0"
    "0 < int64 < 400"
    "500 <= int64 < 1000"
    VARCHAR > "str1"
  2. BinaryLogicalOp 和括号

    复制代码
    "(int64 > 0 && int64 < 400) or (int64 > 500 && int64 < 1000)"
  3. TermExpr 和 UnaryLogicOp

    复制代码
    "int64 not in [1, 2, 3]"
    VARCHAR not in ["str1", "str2"]
  4. TermExpr、BinaryLogicalOp 和 CmpOp(不同字段)

    复制代码
    "int64 in [1, 2, 3] and float != 2"
  5. BinaryLogicalOp 和 CmpOp

    复制代码
    "int64 == 0 || int64 == 1 || int64 == 2"
  6. CmpOp 和 UnaryArithOp 或 BinaryArithOp

    复制代码
    "200+300 < int64 <= 500+500"
  7. MatchOp

    复制代码
    VARCHAR like "prefix%"
    VARCHAR like "%suffix"
    VARCHAR like "%middle%"
    VARCHAR like "_suffix"
  8. JsonArrayOp

    JSON_CONTAINS(identifier, JsonExpr)

    如果 JSON_CONTAINS(第二个参数)的 JSON 表达式是列表,则 identifier(第一个参数)也必须是 list of list,否则该语句始终为 False。

    复制代码
    # {"x": [1,2,3]}
    json_contains(x, 1) # ==> true
    json_contains(x, "a") # ==> false
    
    # {"x": [[1,2,3], [4,5,6], [7,8,9]]}
    json_contains(x, [1,2,3]) # ==> true
    json_contains(x, [3,2,1]) # ==> false

    JSON_CONTAINS_ALL(identifier, JsonExpr)

    JSON_CONTAINS_ALL 中的 JSON 表达式必须始终是列表。

    复制代码
    # {"x": [1,2,3,4,5,7,8]}
    json_contains_all(x, [1,2,8]) # ==> true
    json_contains_all(x, [4,5,6]) # ==> false 6 is not exists

    JSON_CONTAINS_ANY(identifier, JsonExpr)

    JSON_CONTAINS_ANY 中的 JSON 表达式必须始终是列表,否则行为与 JSON_CONTAINS 相同。

    复制代码
    # {"x": [1,2,3,4,5,7,8]}
    json_contains_any(x, [1,2,8]) # ==> true
    json_contains_any(x, [4,5,6]) # ==> true
    json_contains_any(x, [6,9]) # ==> false
  9. ArrayOp

    ARRAY_CONTAINS(identifier, ArrayExpr)

    如果 ARRAY_CONTAINS 的数组表达式(第二个参数)是列表,则 identifier(第一个参数)必须是 list of list,否则该语句始终为 False。

    复制代码
    # 'int_array': [1,2,3]
    array_contains(int_array, 1) # ==> true
    array_contains(int_array, "a") # ==> false

    ARRAY_CONTAINS_ALL(identifier, ArrayExpr)

    ARRAY_CONTAINS_ALL 中的数组表达式必须始终是列表。

    复制代码
    # "int_array": [1,2,3,4,5,7,8]
    array_contains_all(int_array, [1,2,8]) # ==> true
    array_contains_all(int_array, [4,5,6]) # ==> false 6 is not exists

    ARRAY_CONTAINS_ANY(identifier, ArrayExpr)

    ARRAY_CONTAINS_ANY 中的数组表达式必须始终是列表,否则行为与 ARRAY_CONTAINS 相同。

    复制代码
    # "int_array": [1,2,3,4,5,7,8]
    array_contains_any(int_array, [1,2,8]) # ==> true
    array_contains_any(int_array, [4,5,6]) # ==> true
    array_contains_any(int_array, [6,9]) # ==> false

    ARRAY_LENGTH(identifier)

    检查数组中的元素数量。

    复制代码
    # "int_array": [1,2,3,4,5,7,8]
    array_length(int_array) # ==> 7

基础操作符(Basic Operators)

Milvus 提供了一组丰富的基础操作符,用于高效地过滤和查询数据。这些操作符可以帮助你基于标量字段、数值计算、逻辑条件等来精确限定搜索条件。理解这些操作符的用法对于构建精确查询以及提升搜索效率至关重要。

比较操作符(Comparison operators)

比较操作符用于基于相等、不相等或大小关系来过滤数据,适用于数值字段和文本字段。

支持的比较操作符:

  • ==(等于)
  • !=(不等于)
  • >(大于)
  • <(小于)
  • >=(大于等于)
  • <=(小于等于)

示例 1:使用等于(==)过滤

假设你有一个字段 status,希望查找所有 status"active" 的实体,可以使用等于操作符 ==

python 复制代码
filter = 'status == "active"'

示例 2:使用不等于(!=)过滤

查找 status 不等于 "inactive" 的实体:

python 复制代码
filter = 'status != "inactive"'

示例 3:使用大于(>)过滤

查找所有年龄大于 30 的实体:

python 复制代码
filter = 'age > 30'

示例 4:使用小于(<)过滤

查找价格小于 100 的实体:

python 复制代码
filter = 'price < 100'

示例 5:使用大于等于(>=)过滤

查找评分大于等于 4 的所有实体:

python 复制代码
filter = 'rating >= 4'

示例 6:使用小于等于(<=)过滤

查找折扣小于等于 10% 的实体:

python 复制代码
filter = 'discount <= 10'
范围操作符(Range operators)

范围操作符用于根据特定的值集合或范围来过滤数据。

支持的范围操作符:

  • IN:用于匹配某个特定集合或范围内的值
  • LIKE:用于模式匹配(主要用于文本字段)。Milvus 允许你在 VARCHAR 或 JSON 字段上构建 NGRAM 索引来加速文本查询

示例 1:使用 IN 匹配多个值

如果你想查找所有 color"red""green""blue" 的实体:

python 复制代码
filter = 'color in ["red", "green", "blue"]'

当你需要判断某个字段是否属于一个值列表时,这种方式非常有用。

示例 2:使用 LIKE 进行模式匹配

LIKE 操作用于字符串字段的模式匹配,可以匹配文本中不同位置的子串:前缀、中间或后缀。

LIKE 使用 % 作为通配符,可以匹配任意数量的字符(包括 0 个字符)。

在大多数情况下,中缀匹配或后缀匹配的性能明显慢于前缀匹配。如果对性能敏感,需要谨慎使用。

前缀匹配(Starts With)

用于匹配以某个字符串开头的内容,可以将模式放在前面,并用 % 匹配后续任意字符。例如查找所有以 "Prod" 开头的产品:

python 复制代码
filter = 'name LIKE "Prod%"'

该表达式会匹配所有以 "Prod" 开头的产品,例如 "Product A""Product B" 等。

后缀匹配(Ends With)

用于匹配以某个字符串结尾的内容,将 % 放在模式前面。例如查找所有以 "XYZ" 结尾的产品:

python 复制代码
filter = 'name LIKE "%XYZ"'

该表达式会匹配所有以 "XYZ" 结尾的产品,例如 "ProductXYZ""SampleXYZ" 等。

中缀匹配(Contains)

用于匹配字符串中任意位置包含某个模式,在模式两侧都加 %。例如查找所有包含 "Pro" 的产品:

python 复制代码
filter = 'name LIKE "%Pro%"'

该表达式会匹配所有包含 "Pro" 子串的产品,例如 "Product""ProLine""SuperPro"

中缀匹配(Infix Match / Contains)

要进行中缀匹配(即模式可以出现在字符串的任意位置),可以在模式的前后都加上 % 符号。

例如,查找所有名称中包含 "Pro" 的产品:

python 复制代码
filter = 'name LIKE "%Pro%"'

该条件会匹配所有包含子串 "Pro" 的产品,例如 "Product""ProLine""SuperPro"

算术运算符(Arithmetic Operators)

算术运算符允许你基于数值字段的计算结果来构建过滤条件。

支持的算术运算符:

  • +(加法)
  • -(减法)
  • *(乘法)
  • /(除法)
  • %(取模)
  • **(幂运算)

示例 1:使用取模(%)

查找 id 为偶数的实体(即能被 2 整除):

python 复制代码
filter = 'id % 2 == 0'

示例 2:使用幂运算()**

查找 price 的平方大于 1000 的实体:

python 复制代码
filter = 'price ** 2 > 1000'
逻辑运算符(Logical Operators)

逻辑运算符用于将多个条件组合成更复杂的过滤表达式,包括 AND、OR 和 NOT。

支持的逻辑运算符:

  • AND:所有条件都必须成立
  • OR:至少一个条件成立
  • NOT:对条件取反

示例 1:使用 AND 组合条件

查找所有 price > 100stock > 50 的产品:

python 复制代码
filter = 'price > 100 AND stock > 50'

示例 2:使用 OR 组合条件

查找所有颜色为 "red""blue" 的产品:

python 复制代码
filter = 'color == "red" OR color == "blue"'

示例 3:使用 NOT 排除条件

查找所有颜色不是 "green" 的产品:

python 复制代码
filter = 'NOT color == "green"'
IS NULL 和 IS NOT NULL 操作符

IS NULLIS NOT NULL 操作用于根据字段是否包含空值(null)来进行过滤。

  • IS NULL:用于筛选某个字段为 null 的实体,即该字段没有值或值未定义
  • IS NOT NULL:用于筛选某个字段不为 null 的实体,即该字段具有有效值

这些操作符大小写不敏感 ,因此可以写作 IS NULLis null,以及 IS NOT NULLis not null

Milvus 支持对带 null 值的普通标量字段(如字符串或数值)进行过滤。

对于 VARCHAR 字段,空字符串 "" 不会被视为 null 值

查询 description 为 null 的实体:

python 复制代码
filter = 'description IS NULL'

查询 description 不为 null 的实体:

python 复制代码
filter = 'description IS NOT NULL'

查询 description 不为 null 且 price 大于 10 的实体:

python 复制代码
filter = 'description IS NOT NULL AND price > 10'

Milvus 支持对包含 null 值的 JSON 字段进行过滤。JSON 字段在以下情况会被认为是 null:

  • 整个 JSON 对象被显式设置为 None(null),例如:{"metadata": None}
  • JSON 字段在实体中完全缺失

如果 JSON 对象内部的某些字段为 null(例如某个 key 为 null),该 JSON 字段仍然被认为是非 null

例如:

python 复制代码
{"metadata": {"category": None, "price": 99.99}}

即使 category 为 null,该字段仍然不是整体 null。

为了进一步说明 Milvus 如何处理包含 null 值的 JSON 字段,请考虑以下包含 metadata JSON 字段的示例数据:

python 复制代码
data = [
  {
      "metadata": {"category": "electronics", "price": 99.99, "brand": "BrandA"},
      "pk": 1,
      "embedding": [0.12, 0.34, 0.56]
  },
  {
      "metadata": None, # 整个 JSON 对象为 null
      "pk": 2,
      "embedding": [0.56, 0.78, 0.90]
  },
  {  # JSON 字段 metadata 完全缺失
      "pk": 3,
      "embedding": [0.91, 0.18, 0.23]
  },
  {
      "metadata": {"category": None, "price": 99.99, "brand": "BrandA"}, # 部分 key 为 null
      "pk": 4,
      "embedding": [0.56, 0.38, 0.21]
  }
]

示例 1:查询 metadata 为 null 的实体

查询 metadata 字段缺失或显式为 None 的实体:

python 复制代码
filter = 'metadata IS NULL'
text 复制代码
# 示例输出:
# data: [
#     "{'metadata': None, 'pk': 2}",
#     "{'metadata': None, 'pk': 3}"
# ]

示例 2:查询 metadata 不为 null 的实体

查询 metadata 字段不为 null 的实体:

python 复制代码
filter = 'metadata IS NOT NULL'
text 复制代码
# 示例输出:
# data: [
#     "{'metadata': {'category': 'electronics', 'price': 99.99, 'brand': 'BrandA'}, 'pk': 1}",
#     "{'metadata': {'category': None, 'price': 99.99, 'brand': 'BrandA'}, 'pk': 4}"
# ]

Milvus 支持对包含 null 值的 ARRAY 字段进行过滤。ARRAY 字段在以下情况下被认为是 null:

  • 整个 ARRAY 字段被显式设置为 None
  • ARRAY 字段在实体中完全缺失

ARRAY 字段不能包含"部分 null",因为所有元素必须是相同类型。

python 复制代码
data = [
  {
      "tags": ["pop", "rock", "classic"],
      "ratings": [5, 4, 3],
      "pk": 1,
      "embedding": [0.12, 0.34, 0.56]
  },
  {
      "tags": None,  # 整个 ARRAY 为 null
      "ratings": [4, 5],
      "pk": 2,
      "embedding": [0.78, 0.91, 0.23]
  },
  {  # tags 字段完全缺失
      "ratings": [9, 5],
      "pk": 3,
      "embedding": [0.18, 0.11, 0.23]
  }
]

示例 1:查询 tags 为 null 的实体

查询 tags 字段缺失或显式为 None 的实体:

python 复制代码
filter = 'tags IS NULL'
text 复制代码
# 示例输出:
# data: [
#     "{'tags': None, 'ratings': [4, 5], 'embedding': [0.78, 0.91, 0.23], 'pk': 2}",
#     "{'tags': None, 'ratings': [9, 5], 'embedding': [0.18, 0.11, 0.23], 'pk': 3}"
# ]

示例 2:查询 tags 不为 null 的实体

查询 tags 字段不为 null 的实体:

python 复制代码
filter = 'tags IS NOT NULL'
text 复制代码
# 示例输出:
# data: [
#     "{'metadata': {'category': 'electronics', 'price': 99.99, 'brand': 'BrandA'}, 'pk': 1}",
#     "{'metadata': {'category': None, 'price': 99.99, 'brand': 'BrandA'}, 'pk': 4}"
# ]

虽然 Milvus 中的基础运算符主要用于标量字段,但它们同样可以有效应用于 JSON 和 ARRAY 字段中的 key 和索引。

例如,如果你有一个 product 字段,其中包含多个 key,如 pricemodeltags,可以直接通过 key 进行引用:

python 复制代码
filter = 'product["price"] > 1000'

如果你想查找数组中记录的第一个温度值大于某个阈值的记录,可以这样写:

python 复制代码
filter = 'history_temperatures[0] > 30'

Milvus 提供了一系列基础运算符,使你能够灵活地对数据进行过滤与查询。通过组合比较运算符、范围运算符、算术运算符和逻辑运算符,你可以构建强大的过滤表达式,从而高效地缩小搜索范围并获取所需数据。

常见问题(FAQ)

过滤条件中的匹配值列表长度是否有限制?例如:filter='color in ["red", "green", "blue"]',如果列表很长怎么办?

Zilliz Cloud 在过滤条件中对匹配值列表长度没有硬性限制。但如果列表过长,会显著影响查询性能。

如果你的过滤条件包含很长的匹配值列表,或由大量元素组成的复杂表达式,建议使用 Filter Templating(过滤模板) 来提升查询性能。

过滤模板(Filter Templating)

在 Milvus 中,包含大量元素的复杂过滤表达式(尤其是包含 CJK 等非 ASCII 字符的情况)可能会显著影响查询性能。为了解决这一问题,Milvus 引入了过滤表达式模板机制,通过减少复杂表达式的解析时间来提升效率。

过滤表达式模板允许你创建带占位符的过滤表达式,并在查询执行时动态替换为实际值。通过使用模板方式,你可以避免将大规模数组或复杂表达式直接嵌入 filter 中,从而减少解析时间并提升查询性能。

例如,你有一个包含 agecity 两个字段的过滤条件,需要查询年龄大于 25 且居住在"北京"或"上海"的所有人。你可以不直接写死这些值,而是使用模板:

python 复制代码
filter = "age > {age} AND city IN {city}"
filter_params = {"age": 25, "city": ["北京", "上海"]}

这里 {age}{city} 是占位符,在查询执行时会被 filter_params 中的实际值替换。

使用过滤模板的优势:

减少解析时间(Reduced Parsing Time)

通过用占位符替代大型或复杂表达式,系统在解析 filter 时的开销显著降低。

提升查询性能(Improved Query Performance)

解析开销减少后,整体查询性能提升,从而获得更高 QPS 和更低延迟。

更好的扩展性(Scalability)

随着数据规模增长和过滤条件复杂度上升,模板机制能够保证系统仍然保持高效和可扩展。

在 Milvus 的 search 操作中,可以通过 filter 定义过滤表达式,并通过 filter_params 提供占位符的实际值。filter_params 字典用于存放 Milvus 在执行查询时需要替换的动态参数。

python 复制代码
expr = "age > {age} AND city IN {city}"
filter_params = {"age": 25, "city": ["北京", "上海"]}

res = client.search(
    "hello_milvus",
    vectors[:nq],
    filter=expr,
    limit=10,
    output_fields=["age", "city"],
    search_params={"metric_type": "COSINE", "params": {"search_list": 100}},
    filter_params=filter_params,
)

在这个例子中,Milvus 在执行 search 时会将 {age} 替换为 25,将 {city} 替换为 ["北京", "上海"]

同样的模板机制也适用于 Milvus 的 query 操作。在 query 方法中,你可以定义过滤表达式,并通过 filter_params 提供替换值。

python 复制代码
expr = "age > {age} AND city IN {city}"
filter_params = {"age": 25, "city": ["北京", "上海"]}

res = client.query(
    "hello_milvus",
    filter=expr,
    output_fields=["age", "city"],
    filter_params=filter_params
)

通过使用 filter_params,Milvus 能够高效地处理动态值的插入,从而提升查询执行速度。

你也可以在删除操作中使用过滤表达式模板。与 search 和 query 类似,filter 表达式用于定义条件,而 filter_params 用于提供占位符对应的动态值。

python 复制代码
expr = "age > {age} AND city IN {city}"
filter_params = {"age": 25, "city": ["北京", "上海"]}

res = client.delete(
    "hello_milvus",
    filter=expr,
    filter_params=filter_params
)

这种方式可以提升删除操作的性能,尤其是在处理复杂过滤条件时效果更加明显。

过滤表达式模板是优化 Milvus 查询性能的重要工具。通过使用占位符和 filter_params 字典,可以显著减少复杂过滤表达式的解析时间,从而加快查询执行速度,并提升整体系统性能。

JSON 运算符(JSON Operators)

Milvus 支持用于查询和过滤 JSON 字段的高级运算符,非常适合处理复杂的结构化数据。这些运算符可以高效地对 JSON 文档进行查询,使你能够基于 JSON 字段中的特定元素、值或条件来检索实体。本节将介绍 Milvus 中 JSON 专用运算符的使用方式,并通过实际示例说明其功能。

需要注意的是,JSON 字段无法很好地处理复杂的嵌套结构,并且会将所有嵌套结构当作普通字符串处理。因此,在使用 JSON 字段时,建议避免过深的嵌套结构,并尽量保持数据结构扁平化,以获得更优的性能。

可用的 JSON 运算符

Milvus 提供了以下几个强大的 JSON 运算符,用于过滤和查询 JSON 数据:

  • JSON_CONTAINS(identifier, expr):过滤 JSON 字段中包含指定表达式的实体
  • JSON_CONTAINS_ALL(identifier, expr):确保 JSON 字段包含表达式中的所有元素
  • JSON_CONTAINS_ANY(identifier, expr):过滤 JSON 字段中至少包含表达式中的一个元素的实体

JSON_CONTAINS

json_contains 运算符用于检查 JSON 字段中是否存在某个特定元素或子数组。当你需要确认 JSON 数组或对象包含某个值时非常有用。

假设你有一个产品集合,每个产品包含一个 tags 字段,该字段是一个 JSON 数组,例如:

json 复制代码
["electronics", "sale", "new"]

你希望筛选出包含 "sale" 标签的产品:

python 复制代码
# JSON 数据: {"tags": ["electronics", "sale", "new"]}
filter = 'json_contains(product["tags"], "sale")'

在这个例子中,Milvus 会返回所有 tags 字段中包含 "sale" 的产品。

JSON_CONTAINS_ALL

json_contains_all 运算符用于确保目标 JSON 字段中包含指定表达式的所有元素。当你需要同时匹配多个值时非常有用。

继续使用产品标签的例子,如果你希望找到同时包含 "electronics""sale""new" 的产品,可以使用:

python 复制代码
# JSON 数据: {"tags": ["electronics", "sale", "new", "discount"]}
filter = 'json_contains_all(product["tags"], ["electronics", "sale", "new"])'

该查询会返回所有 tags 数组中同时包含这三个指定元素的产品。

JSON_CONTAINS_ANY

json_contains_any 运算符用于筛选 JSON 字段中至少包含指定表达式中任意一个元素的实体。当你希望匹配多个可能值中的任意一个时非常有用。

例如,你希望筛选出标签中包含 "electronics""sale""new" 中任意一个的产品:

python 复制代码
# JSON 数据: {"tags": ["electronics", "sale", "new"]}
filter = 'json_contains_any(tags, ["electronics", "new", "clearance"])'

在这种情况下,Milvus 会返回所有 tags 中至少包含列表中一个标签的产品。即使某个产品只匹配其中一个标签,也会被包含在结果中。

ARRAY 运算符(ARRAY Operators)

Milvus 提供了一组强大的数组字段查询运算符,使你能够基于数组内容对实体进行过滤与检索。

数组中的所有元素必须是同一类型,并且数组中的嵌套结构会被当作普通字符串处理。因此,在使用 ARRAY 字段时,建议避免过深的嵌套结构,并尽量保持数据结构扁平化,以获得最佳性能。

可用的 ARRAY 运算符

Milvus 提供以下用于数组字段精细查询的运算符:

  • ARRAY_CONTAINS(identifier, expr):检查数组字段中是否存在某个指定元素
  • ARRAY_CONTAINS_ALL(identifier, expr):确保数组字段包含指定列表中的所有元素
  • ARRAY_CONTAINS_ANY(identifier, expr):检查数组字段中是否包含指定列表中的任意一个元素
  • ARRAY_LENGTH(identifier):返回数组字段的元素个数,并可与比较运算符结合用于过滤

ARRAY_CONTAINS

ARRAY_CONTAINS 用于检查数组字段中是否包含某个指定元素。当你需要查找数组中存在某个特定值的实体时非常有用。

假设你有一个数组字段 history_temperatures,用于记录不同年份的最低气温。若要查找所有数组中包含 23 的实体,可以使用:

python 复制代码
filter = 'ARRAY_CONTAINS(history_temperatures, 23)'

该查询会返回所有 history_temperatures 数组中包含值 23 的实体。

ARRAY_CONTAINS_ALL

ARRAY_CONTAINS_ALL 用于确保数组字段同时包含指定列表中的所有元素。当你需要匹配多个值同时存在时非常有用。

如果你想查找 history_temperatures 数组中同时包含 2324 的所有实体,可以使用:

python 复制代码
filter = 'ARRAY_CONTAINS_ALL(history_temperatures, [23, 24])'

该查询会返回所有数组中同时包含这两个值的实体。

ARRAY_CONTAINS_ANY

ARRAY_CONTAINS_ANY 用于检查数组字段中是否包含指定列表中的任意一个元素。当你希望匹配多个可能值中的任意一个时非常有用。

要查找 history_temperatures 数组中包含 2324 的实体,可以使用:

python 复制代码
filter = 'ARRAY_CONTAINS_ANY(history_temperatures, [23, 24])'

该查询会返回所有数组中至少包含一个指定值的实体(23 或 24)。

ARRAY_LENGTH

ARRAY_LENGTH 返回数组字段的长度(元素个数)。它只接受一个参数:数组字段标识符。

要查找 history_temperatures 数组长度小于 10 的所有实体,可以使用:

python 复制代码
filter = 'ARRAY_LENGTH(history_temperatures) < 10'

该查询会返回所有 history_temperatures 数组元素个数少于 10 的实体。

结构数组运算符(StructArray Operators)

实体中的数组结构体(Array of Structs,或 StructArray)用于存储一组有序的 Struct 元素。StructArray 中的每个 Struct 都遵循相同的预定义 schema,该 schema 由多个向量字段和标量字段组成。当 Struct 中的某个标量子字段被建立索引后,你可以使用 element filter 和 match 系列运算符对其进行标量过滤。

element filter 用于选择那些 StructArray 字段中至少有一个值满足指定谓词的实体;而 match 系列运算符则用于查找 StructArray 中满足谓词条件的元素数量或比例符合要求的实体。

在针对 $[subField] 构建谓词时,如果处理的是大规模数据集,必须确保该子字段已建立索引,因为这些运算符需要对候选实体中的数组元素逐个进行遍历计算。

当你需要判断 StructArray 字段中是否存在满足某个条件的元素时,可以使用 element filter。

python 复制代码
element_filter(chunks, $[text] LIKE "Red%")

如上所示,该表达式会返回所有 chunks 字段中,至少存在一个 text 子字段以 "Red" 开头的实体。

其中,第一个参数是 StructArray 字段名,第二个参数是应用于 Struct 子字段的谓词条件。

你可以使用比较运算符、范围运算符和算术运算符来构建条件,也可以使用逻辑运算符进行组合(参考 Basic Operators)。

需要注意的是,当你同时构建"实体级过滤条件"和 element filter 时,element filter 必须放在表达式的末尾,如下所示:

python 复制代码
# 正确
id > 0 && element_filter(chunks, $[x] > 1)

# 错误(会导致报错)
element_filter(chunks, $[x] > 1) && id > 0

Match 系列运算符同样可以用于 StructArray 字段。不同于简单判断"是否存在",它们可以用于判断满足条件的元素数量或比例。

  • MATCH_ANY(identifier, predicate):返回至少有一个 chunk 满足条件(语义上等价于 element_filter)
  • MATCH_ALL(identifier, predicate):返回所有 chunk 都满足条件
  • MATCH_LEAST(identifier, predicate, k):返回至少有 k 个 chunk 满足条件
  • MATCH_MOST(identifier, predicate, k):返回最多 k 个 chunk 满足条件
  • MATCH_EXACT(identifier, predicate, k):返回恰好有 k 个 chunk 满足条件

MATCH_ANY

如果数组中至少有一个元素满足谓词,该表达式即为 true,相当于对所有数组元素进行逻辑 OR。

MATCH_ANY 与 element filter 在语义上等价,可以互换使用。当你需要表达 count(matches) >= 1 时,应使用该运算符。

返回所有任意部分以 "Red" 开头的文档:

python 复制代码
MATCH_ANY(chunks, $[text] LIKE 'Red%')

MATCH_ALL

仅当数组中所有元素都满足谓词时返回 true。

当你需要表达 count(matches) == total elements 时使用该运算符。

python 复制代码
MATCH_ALL(chunks, $[text] LIKE 'Red%')

MATCH_LEAST

该运算符是一个数量过滤器:当满足谓词的元素数量 ≥ k 时返回 true。

当你需要表达 count(matches) >= k 时使用。

python 复制代码
MATCH_LEAST(chunks, $[text] LIKE 'Red%', 3)

MATCH_MOST

该运算符用于过滤满足条件的元素数量 ≤ k 的情况。

它常用于过滤掉某个关键词过度集中(噪声过多)的实体。

python 复制代码
MATCH_MOST(chunks, $[text] LIKE 'Red%', 3)

MATCH_EXACT

这是最严格的数量匹配运算符:只有当满足条件的元素数量 恰好等于 k 时才返回 true。

python 复制代码
MATCH_EXACT(chunks, $[text] LIKE 'Red%', 3)

随机采样(Random Sampling)

在处理大规模数据集时,你通常不需要处理全部数据就能获得洞察或测试过滤逻辑。随机采样提供了一种解决方案,它允许你使用具有统计代表性的数据子集,从而显著减少查询时间和资源消耗。

随机采样在 segment(数据段)级别运行,在保证集合数据分布随机性的同时,实现高效性能。

关键使用场景:

  • 数据探索:以最小资源消耗快速预览集合结构与内容
  • 开发测试:在完整部署前,用可控的数据样本测试复杂过滤逻辑
  • 资源优化:降低探索性查询与统计分析的计算成本

语法

python 复制代码
filter = "RANDOM_SAMPLE(sampling_factor)"

参数:

  • sampling_factor:采样因子,范围为 (0, 1),不包含边界值。
    例如 RANDOM_SAMPLE(0.001) 表示大约选取 0.1% 的结果。

重要规则:

  • 表达式大小写不敏感(RANDOM_SAMPLE 或 random_sample)
  • 采样因子必须在 (0, 1) 范围内(不包含端点)

随机采样操作符必须通过逻辑 AND 与其他过滤表达式组合使用。在组合过滤条件时,Milvus 会先执行其他条件过滤,然后再对结果集进行随机采样。

python 复制代码
# 正确:先过滤,再采样
filter = 'color == "red" AND RANDOM_SAMPLE(0.001)'
# 处理逻辑:先找出所有 red 项 → 再从中采样 0.1%

# 错误:使用 OR 在逻辑上不成立
filter = 'color == "red" OR RANDOM_SAMPLE(0.001)'  # ❌ 无效逻辑
# 含义变为:"red 数据 OR 全量随机采样"------这是无意义的

示例 1:数据探索

快速预览集合结构:

python 复制代码
from pymilvus import MilvusClient

client = MilvusClient(uri="http://localhost:19530")

# 从整个集合中采样约 1%
result = client.query(
    collection_name="product_catalog",
    filter="RANDOM_SAMPLE(0.01)",
    output_fields=["id", "product_name"],
    limit=10
)

print(f"从集合中采样得到 {len(result)} 个产品")

示例 2:结合过滤的随机采样

在可控子集上测试过滤逻辑:

python 复制代码
# 先按类别和价格过滤,再对结果采样 0.5%
filter_expression = 'category == "electronics" AND price > 100 AND RANDOM_SAMPLE(0.005)'

result = client.query(
    collection_name="product_catalog",
    filter=filter_expression,
    output_fields=["product_name", "price", "rating"],
    limit=10
)

print(f"在采样中找到 {len(result)} 个电子产品")

示例 3:快速统计分析

在过滤数据上进行快速统计分析:

python 复制代码
# 从约 0.1% 的高端客户数据中获取洞察
filter_expression = 'customer_tier == "premium" AND region == "North America" AND RANDOM_SAMPLE(0.001)'

result = client.query(
    collection_name="customer_profiles",
    filter=filter_expression,
    output_fields=["purchase_amount", "satisfaction_score", "last_purchase_date"],
    limit=10
)

# 对样本进行分析
if result:
    average_purchase = sum(r["purchase_amount"] for r in result) / len(result)
    average_satisfaction = sum(r["satisfaction_score"] for r in result) / len(result)
    
    print(f"样本大小: {len(result)}")
    print(f"平均购买金额: ${average_purchase:.2f}")
    print(f"平均满意度: {average_satisfaction:.2f}")

示例 4:结合向量搜索

在过滤场景中使用随机采样:

python 复制代码
# 在采样子集内搜索相似产品
search_results = client.search(
    collection_name="product_catalog",
    data=[[0.1, 0.2, 0.3, 0.4, 0.5]],  # 查询向量
    filter='category == "books" AND RANDOM_SAMPLE(0.01)',
    search_params={"metric_type": "L2", "params": {}},
    output_fields=["title", "author", "price"],
    limit=10
)

print(f"在采样中找到 {len(search_results[0])} 本相似书籍")

最佳实践

  • 从小开始:初始探索使用较小采样率(0.001--0.01)
  • 开发流程:开发阶段使用采样,生产查询移除采样
  • 统计有效性:更大的样本能提供更准确的统计结果
  • 性能测试:监控查询性能并按需调整采样比例

Geometry Operators

Milvus 支持一组用于 GEOMETRY 字段空间过滤的运算符,这些运算符对于管理和分析几何数据非常重要。它们允许你基于对象之间的几何关系来检索实体。

所有几何运算符都接受两个几何参数:

  • 一个是集合 schema 中定义的 GEOMETRY 字段名称
  • 另一个是使用 Well-Known Text(WKT)格式表示的目标几何对象

在表达式中对 GEOMETRY 字段进行过滤:

通用形式:

python 复制代码
{operator}(geo_field, '{wkt}')

基于距离的形式:

python 复制代码
ST_DWITHIN(geo_field, '{wkt}', distance)

参数说明

  • operator:几何运算符(如 ST_CONTAINS、ST_INTERSECTS)

    • 必须全部大写或全部小写
  • geo_field:GEOMETRY 字段名称

  • '{wkt}':WKT 格式的几何对象

  • distance:仅用于 ST_DWITHIN 的距离阈值

⚠️ 运算符名称必须全部大写或全部小写,不能混合大小写。

运算符 描述 示例
ST_EQUALS(A, B) / st_equals(A, B) 如果两个几何对象在空间上完全一致(点集和维度相同),返回 TRUE 两个几何 A 和 B 在空间上是否完全相同?
ST_CONTAINS(A, B) / st_contains(A, B) 如果 A 完全包含 B,并且内部至少有一个公共点,则返回 TRUE 城市边界(A)是否包含某个公园(B)?
ST_CROSSES(A, B) / st_crosses(A, B) 如果 A 和 B 部分相交但互不完全包含,则返回 TRUE 两条道路是否在交叉点相交?
ST_INTERSECTS(A, B) / st_intersects(A, B) 如果 A 和 B 至少有一个公共点,则返回 TRUE(最通用) 搜索区域是否与商店位置相交?
ST_OVERLAPS(A, B) / st_overlaps(A, B) 如果 A 和 B 同维度、部分重叠且互不包含,则返回 TRUE 两块地是否重叠?
ST_TOUCHES(A, B) / st_touches(A, B) 如果 A 和 B 共享边界但内部不相交,则返回 TRUE 相邻房产是否接壤?
ST_WITHIN(A, B) / st_within(A, B) 如果 A 完全位于 B 内部,则返回 TRUE(ST_CONTAINS 的逆) 兴趣点是否在搜索范围内?
ST_DWITHIN(A, B, distance) / st_dwithin(A, B, distance) 如果 A 与 B 距离 ≤ distance,则返回 TRUE(B 仅支持 Point,单位米) 5000 米内的点

ST_DWITHIN 特别说明:

  • B 当前仅支持 Point
  • 距离单位:
  • 常用于"附近搜索 / LBS 场景"

总结理解:

  • ST_INTERSECTS:只要碰到就算
  • ST_CONTAINS / WITHIN:包含关系(反方向)
  • ST_TOUCHES:只碰边不进内部
  • ST_OVERLAPS:部分重叠
  • ST_CROSSES:线性交叉
  • ST_EQUALS:完全一样
  • ST_DWITHIN:距离范围

ST_EQUALS / st_equals

ST_EQUALS 运算符在两个几何对象在空间上完全一致时返回 TRUE,这意味着它们具有相同的点集合和维度。

该操作常用于验证两个几何对象是否表示完全相同的位置和形状。

假设你想检查一个已存储的几何对象(如点或多边形)是否与目标几何完全相同。例如,将一个存储的点与某个兴趣点进行比较。

python 复制代码
# 用于检查几何是否匹配某个特定点的过滤表达式
filter = "ST_EQUALS(geo_field, 'POINT(10 20)')"

ST_CONTAINS / st_contains

ST_CONTAINS 运算符在第一个几何对象完全包含第二个几何对象时返回 TRUE。

它常用于查找"点是否在多边形内"或"小多边形是否在大多边形内"。

假设你有一个城市区域集合,希望查找某个兴趣点(例如餐厅)是否位于某个行政区边界内。

python 复制代码
# 查找完全位于某个多边形内的几何对象
filter = "ST_CONTAINS(geo_field, 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))')"

ST_CROSSES / st_crosses

ST_CROSSES 运算符在两个几何对象的交集形成的几何维度低于原始对象维度时返回 TRUE。

通常用于线穿过多边形或线与线交叉的情况。

你想找出所有穿越某条边界线或进入保护区域的徒步路径(线)。

python 复制代码
# 查找与线相交的几何对象
filter = "ST_CROSSES(geo_field, 'LINESTRING(5 0, 5 10)')"

ST_INTERSECTS / st_intersects

ST_INTERSECTS 运算符在两个几何对象至少有一个点(边界或内部)重合时返回 TRUE。

它是最通用的空间关系判断操作。

如果你有一组道路数据,想找出所有与新规划道路相交或接触的道路,可以使用该操作。

python 复制代码
# 查找与指定线相交的几何对象
filter = "ST_INTERSECTS(geo_field, 'LINESTRING (1 1, 2 2)')"

ST_OVERLAPS / st_overlaps

ST_OVERLAPS 运算符在两个同维度几何对象发生部分重叠,且交集本身维度与原对象一致,但不完全相等时返回 TRUE。

你有一组重叠的销售区域,想找出所有与新销售区域部分重叠的区域。

python 复制代码
# 查找与多边形部分重叠的几何对象
filter = "ST_OVERLAPS(geo_field, 'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))')"

ST_TOUCHES / st_touches

ST_TOUCHES 运算符在两个几何对象仅边界接触、内部不相交时返回 TRUE。

用于检测邻接关系。

在房产地图中查找与公园边界直接相邻但不重叠的地块。

python 复制代码
# 查找仅在边界接触的几何对象
filter = "ST_TOUCHES(geo_field, 'LINESTRING(0 0, 1 1)')"

ST_WITHIN / st_within

ST_WITHIN 运算符在第一个几何对象完全位于第二个几何对象内部或边界上时返回 TRUE。

它是 ST_CONTAINS 的逆操作。

查找所有完全位于大型公园内部的小型住宅区域。

python 复制代码
# 查找完全位于某个多边形内的几何对象
filter = "ST_WITHIN(geo_field, 'POLYGON((110 38, 115 38, 115 42, 110 42, 110 38))')"

ST_DWITHIN / st_dwithin

ST_DWITHIN 运算符在几何 A 与几何 B 的距离小于或等于指定值(单位:米)时返回 TRUE。

目前几何 B 必须是 Point 类型。

假设你有门店位置数据,希望查找距离某个客户位置 5000 米以内的所有门店。

python 复制代码
# 查找距离点(120 30)5000米以内的所有对象
filter = "ST_DWITHIN(geo_field, 'POINT(120 30)', 5000)"

全文检索(Full Text Search)

全文检索(Full Text Search)是一项用于从文本数据集中检索包含特定词语或短语的文档,并根据相关性对结果进行排序的功能。

相比于语义搜索(Semantic Search),全文检索能够弥补其不足。由于语义搜索更关注语义相似性,因此可能会忽略用户指定的精确关键词,而全文检索能够确保返回最准确、最符合上下文的搜索结果。

此外,全文检索还简化了向量搜索流程。用户无需手动生成向量嵌入(Vector Embeddings),只需输入原始文本,Milvus 就会自动将文本转换为稀疏向量(Sparse Embeddings)进行检索。

全文检索采用 BM25 算法进行相关性评分,因此特别适用于 检索增强生成(Retrieval-Augmented Generation,RAG) 场景。在这类场景下,它能够优先返回与查询关键词最匹配的文档。

将全文检索与基于语义的**稠密向量检索(Dense Vector Search)**结合使用,可以进一步提升搜索结果的准确性和相关性。

BM25 是信息检索系统(Information Retrieval,IR)中应用最广泛的相关性评分函数之一,Milvus 将其集成到检索流程中,以提供按照相关性排序的高质量文本搜索结果。

Milvus 中的全文检索整体流程如下:

  1. 原始文本输入(Raw text input)

    • 插入文本数据,或直接使用自然语言作为查询。
    • 无需任何 Embedding 模型。
  2. 文本分析(Text analysis)

    • Milvus 使用 Analyzer(文本分析器) 将文本处理为可索引、可检索的词项(Terms)。
  3. BM25 函数处理(BM25 function processing)

    • 内置 BM25 Function 会将这些词项转换为适用于 BM25 计算的稀疏向量表示(Sparse Vector Representation)
  4. Collection 存储(Collection store)

    • Milvus 将生成的稀疏向量存储到 Collection 中,以支持高效检索和排序。
  5. BM25 相关性评分(BM25 relevance scoring)

    • 查询时,Milvus 使用 BM25 评分函数计算每篇文档与查询之间的相关性,并返回按相关性排序后的搜索结果。

要使用全文检索,需要完成以下几个主要步骤:

  1. 创建 Collection

    • 创建所需字段,并定义 BM25 Function,用于将原始文本转换为稀疏向量。
  2. 插入数据

    • 将原始文本数据写入 Collection。
  3. 执行搜索

    • 使用自然语言文本作为查询,根据 BM25 相关性返回排序后的检索结果。

要启用基于 BM25 的全文检索,需要完成以下准备工作:

  • 创建包含所需字段(Field)的 Collection;
  • 定义一个 BM25 Function,用于生成稀疏向量;
  • 配置索引(Index);
  • 创建 Collection。

Collection 的 Schema 至少需要包含以下三个字段:

  • Primary Field(主键字段)用于唯一标识 Collection 中的每一个实体(Entity)。

  • Text Field(VARCHAR)用于存储原始文本。

    必须开启:

    python 复制代码
    enable_analyzer=True

    这样 Milvus 才会对文本进行分析,以支持 BM25 相关性排序。

    默认情况下,Milvus 使用 Standard Analyzer(标准分析器) 对文本进行分词。

  • Sparse Vector Field(SPARSE_FLOAT_VECTOR)

    用于存储由 BM25 Function 自动生成的稀疏向量(Sparse Embeddings)。

    python 复制代码
    from pymilvus import MilvusClient, DataType, Function, FunctionType
    
    client = MilvusClient(
        uri="http://localhost:19530",
        token="root:Milvus"
    )
    
    schema = client.create_schema()
    
    schema.add_field(
        field_name="id",
        datatype=DataType.INT64,
        is_primary=True,
        auto_id=True
    )  # 主键字段
    
    schema.add_field(
        field_name="text",
        datatype=DataType.VARCHAR,
        max_length=1000,
        enable_analyzer=True
    )  # 文本字段
    
    schema.add_field(
        field_name="sparse",
        datatype=DataType.SPARSE_FLOAT_VECTOR
    )  # 稀疏向量字段(Sparse Vector 无需指定 dim)

上述配置中:

  • id

    • 作为 Collection 的主键(Primary Key)。
    • 设置 auto_id=True 后,Milvus 会自动生成主键。
  • text

    • 用于存储全文检索所需的原始文本数据。
    • 字段类型必须为 VARCHAR,这是 Milvus 用于文本存储的字符串类型。
  • sparse

    • 用于保存 Milvus 在全文检索过程中自动生成的稀疏向量(Sparse Embeddings)。
    • 字段类型必须为 SPARSE_FLOAT_VECTOR

BM25 Function 用于将经过分词(Tokenize)的文本转换为支持 BM25 评分的稀疏向量(Sparse Vectors)。

定义 Function,并将其添加到 Schema 中:

python 复制代码
bm25_function = Function(
    name="text_bm25_emb", # Function 名称
    input_field_names=["text"], # 存放原始文本的 VARCHAR 字段
    output_field_names=["sparse"], # 用于存放生成稀疏向量的 SPARSE_FLOAT_VECTOR 字段
    function_type=FunctionType.BM25, # 设置为 BM25
)

schema.add_function(bm25_function)
参数 说明
name Function 的名称。该 Function 会将 text 字段中的原始文本转换为兼容 BM25 的稀疏向量,并将生成结果存储到 sparse 字段。
input_field_names 需要进行文本→稀疏向量 转换的 VARCHAR 字段名称。对于 FunctionType.BM25,该参数仅支持指定一个字段。
output_field_names 用于存储内部生成稀疏向量的字段名称。对于 FunctionType.BM25,该参数仅支持指定一个字段。
function_type 指定使用的 Function 类型。全文检索必须设置为 FunctionType.BM25

如果有多个 VARCHAR 字段需要进行 BM25 处理,则需要为每个字段分别定义一个 BM25 Function,并为每个 Function 指定唯一的名称和对应的输出字段。

定义好包含必要字段和内置 Function 的 Schema 后,接下来需要为 Collection 配置索引。

python 复制代码
index_params = client.prepare_index_params()

index_params.add_index(
    field_name="sparse",

    index_type="SPARSE_INVERTED_INDEX",
    metric_type="BM25",
    params={
        "inverted_index_algo": "DAAT_MAXSCORE",
        "bm25_k1": 1.2,
        "bm25_b": 0.75
    }

)
参数 说明
field_name 需要建立索引的向量字段名称。对于全文检索,应指定存储 BM25 生成稀疏向量的字段,本例中为 sparse
index_type 创建的索引类型。AUTOINDEX 允许 Milvus 自动优化索引配置。如果希望更精细地控制索引参数,可以选择 Milvus 支持的其他稀疏向量索引类型。更多信息请参阅 Indexes supported in Milvus
metric_type 全文检索必须设置为 BM25
params 与索引相关的附加参数,以字典形式传入。

params.inverted_index_algo

用于指定倒排索引(Inverted Index)的构建与查询算法。

可选值如下:

"DAAT_MAXSCORE"(默认)

采用 Document-at-a-Time(DAAT) 查询策略,并结合 MaxScore 算法进行优化。

对于 较大的 Top-K 查询包含大量查询词的场景,该算法通常具有更好的性能。

MaxScore 会根据每个词项(Term)的最大贡献分数(Maximum Impact Score),将词项划分为关键词项(Essential Terms)非关键词项(Non-essential Terms),优先处理能够影响 Top-K 排名的词项,并跳过那些几乎不会影响最终结果的词项和文档,从而减少计算量,提高查询效率。


"DAAT_WAND"

采用 Document-at-a-Time(DAAT) 查询策略,并结合 WAND(Weighted AND) 算法进行优化。

WAND 利用词项的最大贡献分数跳过竞争力不足的文档,从而减少需要评分的文档数量。

虽然每命中文档的计算开销高于 MaxScore,但对于 Top-K 较小或**查询词较少(短查询)**的场景,WAND 通常更加高效。


"TAAT_NAIVE"

采用最基础的 Term-at-a-Time(TAAT) 查询策略。

虽然其性能通常低于 DAAT_MAXSCOREDAAT_WAND,但它具有一个独特优势:

不同于 DAAT 系列算法会缓存最大贡献分数(Maximum Impact Score),并且缓存不会随着 Collection 全局平均文档长度(avgdl)的变化而更新,TAAT_NAIVE 会根据当前的 avgdl 动态计算评分,因此能够自动适应 Collection 的变化。


params.bm25_k1

控制词频饱和度(Term Frequency Saturation)

较大的值会提高词频(Term Frequency)在文档相关性评分中的影响。

取值范围:

text 复制代码
[1.2, 2.0]

params.bm25_b

控制**文档长度归一化(Document Length Normalization)**的程度。

通常取值范围为 0~1 ,默认推荐值约为 0.75

  • 值为 1:不进行文档长度归一化。
  • 值为 0:进行完全的文档长度归一化。

现在,可以使用前面定义好的 Schema 和索引参数创建 Collection。

python 复制代码
client.create_collection(
    collection_name='my_collection',
    schema=schema,
    index_params=index_params
)

完成 Collection 和索引的创建后,即可开始插入文本数据。

在这一过程中,你只需要提供原始文本(Raw Text),无需手动生成向量。前面定义的内置 BM25 Function 会自动为每条文本生成对应的稀疏向量(Sparse Vector)。

python 复制代码
client.insert('my_collection', [
    {'text': 'information retrieval is a field of study.'},
    {'text': 'information retrieval focuses on finding relevant information in large datasets.'},
    {'text': 'data mining and information retrieval overlap in research.'},
])

数据插入完成后,即可使用**原始文本查询(Raw Text Query)**执行全文检索。

Milvus 会自动将查询文本转换为稀疏向量,并使用 BM25 算法对匹配结果进行相关性评分,最终返回 Top-K(limit 搜索结果。

如果希望在搜索结果中高亮匹配的关键词,可以配置 Text Highlighter(文本高亮器) 。详情请参阅 Text Highlighter

python 复制代码
res = client.search(
    collection_name='my_collection',
    data=['whats the focus of information retrieval?'],
    anns_field='sparse',
    output_fields=['text'],  # 返回的字段;不能返回 sparse 字段
    limit=3,
)

print(res)
参数 说明
search_params 包含搜索参数的字典。
params.drop_ratio_search 搜索过程中忽略低重要性词项(Low-importance Terms)的比例。更多信息请参阅 Sparse Vector
data 自然语言查询文本。Milvus 会自动使用 BM25 Function 将文本转换为稀疏向量,因此无需提供预先计算好的向量
anns_field 存储内部生成稀疏向量的字段名称。
output_fields 搜索结果中需要返回的字段列表。支持除 BM25 生成的稀疏向量字段 之外的所有字段。通常包括主键字段(如 id)和原始文本字段(如 text)。更多信息请参阅 FAQ
limit 返回的 Top-K 搜索结果数量。

FAQ

  1. 全文检索中,能否输出或访问 BM25 Function 生成的稀疏向量?

    不能。

    BM25 Function 生成的稀疏向量仅供全文检索内部使用,无法直接访问或作为搜索结果输出。

    具体来说:

    • BM25 Function 会在内部生成稀疏向量,用于文档排序和检索。
    • 这些向量存储在 sparse 字段中,但不能 包含在 output_fields 中。
    • 你只能返回原始文本字段和元数据,例如 idtext 等。

    例如:

    python 复制代码
    # ❌ 错误示例:不能返回 sparse 字段
    client.search(
        collection_name='my_collection',
        data=['query text'],
        anns_field='sparse',
        output_fields=['text', 'sparse'],  # 'sparse' 会导致报错
        limit=3,
        search_params=search_params
    )
    python 复制代码
    # ✅ 正确示例:仅返回文本字段
    client.search(
        collection_name='my_collection',
        data=['query text'],
        anns_field='sparse',
        output_fields=['text'],
        limit=3,
        search_params=search_params
    )
  2. 既然无法访问稀疏向量,为什么还需要定义 Sparse Vector 字段?

    Sparse Vector 字段充当的是 Milvus 内部的搜索索引,其作用类似于数据库索引(Index),用户通常不会直接与其交互。

    设计原因:

    • 关注点分离(Separation of Concerns)

      用户只需要处理文本的输入与输出,Milvus 负责内部的向量生成、存储和检索过程。

    • 性能优化(Performance)

      稀疏向量会在数据写入时提前计算并保存,因此查询时无需重新计算,可直接用于 BM25 排序,从而获得更高的检索性能。

    • 更好的用户体验(User Experience)

      Milvus 将复杂的向量计算过程封装在文本检索接口之后,用户无需理解底层向量操作即可完成全文检索。

  3. 如果确实需要访问稀疏向量怎么办?

    如果你的业务需要直接获取或操作稀疏向量,可以采用以下方式:

    • 使用 Sparse Vector 功能,而不是 Full Text Search。
    • 为需要自定义稀疏向量处理流程的场景,创建单独的 Collection。

理解BM25

Milvus 的 Full Text Search + BM25 + Sparse Vector 这一套,其实混合了三个概念:

  1. BM25:一种文本相关性评分算法
  2. 稀疏向量:一种表示文本的数学形式
  3. Milvus 为什么把 BM25 转成稀疏向量:为了统一用向量数据库做检索
BM25 本质是什么?

简单说:

BM25 是一种判断"两个文本有多相关"的算法。

它诞生于传统搜索引擎领域,比如 Google 早期、Lucene、Elasticsearch 的关键词搜索。

例如你搜索:

复制代码
information retrieval

数据库里面有:

复制代码
文档 A:
Information retrieval is a field of study.

文档 B:
Machine learning is widely used in computer vision.

文档 C:
Information retrieval focuses on finding relevant information.

BM25 会计算:

复制代码
查询:
information retrieval

          BM25分数
文档 A       8.5
文档 C       7.9
文档 B       0.1

然后排序返回:

复制代码
A
C
B
BM25 怎么计算相关性?

BM25 本质上是 TF-IDF 的改进版本。先理解两个概念:

TF(Term Frequency)词频。

意思:

一个词在当前文档出现次数。

例如:

文档:

复制代码
I love apple.
Apple is good.
I eat apple every day.

统计:

出现次数
apple 3
love 1
good 1
eat 1

apple 出现很多,所以它可能和主题有关。

但是有问题,如果搜索:

复制代码
the

所有英文文章都有:

复制代码
the
the
the

难道它重要?所以需要IDF(Inverse Document Frequency)逆文档频率。

意思:

一个词越稀有,价值越高。

例如100万篇文章:

复制代码
the 出现 999999 篇

quantum 出现 100 篇

那么:

复制代码
the:
    很常见
    信息量低

quantum:
    很稀有
    信息量高

所以:

复制代码
quantum 权重大
the 权重小

BM25 综合:

复制代码
BM25 ≈ TF × IDF + 文档长度修正

所以一个词:

  • 出现次数多 ✅
  • 不是常见词 ✅
  • 文档长度合理 ✅

最终得分高。

那为什么不用 Embedding?

因为 BM25 和向量搜索解决的问题不同。

比如搜索:

复制代码
苹果手机维修

BM25看关键词:

复制代码
苹果
手机
维修

文档:

复制代码
苹果手机维修教程

匹配非常高。

但是:

复制代码
iPhone故障处理指南

可能匹配不到。

Embedding 后:

复制代码
苹果手机维修

↓


[0.023,
 0.187,
 -0.332,
 ...
]

它理解:

复制代码
苹果手机维修

≈

iPhone故障处理

因为语义相似。

但是如果搜索:

复制代码
CVE-2025-12345漏洞

这种精确编号向量搜索可能反而不好。

所以现在 RAG 里面经常:

复制代码
BM25
 +
Dense Vector Search

一起用,也就是:

复制代码
关键词准确性
+
语义理解能力
稀疏向量是什么?

普通 Embedding,比如 OpenAI embedding:

复制代码
"hello world"

↓

[
0.023,
0.187,
-0.332,
0.981,
...
]

假设:

复制代码
维度 = 1536

这种叫:

Dense Vector(稠密向量)

特点:每个位置都有值。

而稀疏向量Sparse Vector大量位置是 0。

假设词库:

复制代码
0: apple
1: banana
2: computer
3: retrieval
4: database
...
1000000: xxx

文本:

复制代码
information retrieval database

转换:

复制代码
[
0,
0,
0,
0.8,
0.6,
0,
0,
...
]

实际只存:

json 复制代码
{
  "3":0.8,
  "4":0.6
}

而不是存:

复制代码
100万个数字

所以稀疏向量:

复制代码
维度巨大
但是非零元素很少

例如:

复制代码
维度:
1000000

实际有效:
10个

所以叫Sparse。

BM25 为什么生成稀疏向量?

这是 Milvus 的设计重点,传统搜索:

复制代码
文本

↓

分词

↓

倒排索引

↓

BM25评分

↓

结果

例如 Elasticsearch:

复制代码
"information retrieval"

↓

{
 information: 文档1,文档5
 retrieval: 文档1,文档8
}

Milvus 想把它统一到向量检索体系,于是:

复制代码
文本

↓

Analyzer分词

↓

BM25 Function

↓

Sparse Vector

↓

SPARSE_INVERTED_INDEX

↓

搜索

例如文本:

复制代码
information retrieval

变成:

复制代码
{
 "information": 0.8,
 "retrieval": 1.2
}

这就是稀疏向量。

一句话总结,

概念 是什么
BM25 判断两个文本关键词相关程度的算法
TF-IDF BM25的前身,通过词频和稀有程度计算权重
Dense Vector Embedding模型生成的语义向量
Sparse Vector 关键词权重组成的高维稀疏表示
BM25 Function 把文本转换成BM25可计算的Sparse Vector
Milvus Full Text Search 用向量数据库实现传统搜索引擎能力
复制代码
传统搜索:
    
文本
 |
分词
 |
倒排索引
 |
BM25


Milvus:

文本
 |
Analyzer
 |
BM25 Function
 |
Sparse Vector
 |
SPARSE_INVERTED_INDEX
 |
BM25 Search

所以 Milvus 的全文检索,本质上就是:

把 Elasticsearch 那套关键词检索能力,包装成了 Milvus 里的稀疏向量检索能力。

这也是为什么 RAG 场景里现在经常是:

复制代码
用户问题
    |
    +----------------+
    |                |
 BM25检索        Dense向量检索
    |                |
关键词匹配       语义匹配
    |                |
    +-------融合-----+
             |
          Rerank
             |
          LLM生成

这就是所谓 Hybrid Search(混合检索)

文本匹配(Text Match)

Milvus 中的**文本匹配(Text Match)**功能可以基于指定词语对文档进行精确检索。

该功能主要用于**过滤搜索(Filtered Search)场景,用于满足特定条件。它可以结合标量过滤(Scalar Filtering)**进一步缩小查询范围,从而在满足标量条件的数据中执行向量相似度搜索。

文本匹配关注的是:

查询词是否在文档中精确出现。

不会对匹配文档进行相关性评分

如果希望根据查询词的语义含义 以及关键词重要程度 ,检索最相关的文档,推荐使用 Full Text Search(全文检索)

Milvus 集成了 Tantivy,用于提供底层的**倒排索引(Inverted Index)**和基于词项(Term)的文本搜索能力。

对于每条文本数据,Milvus 会按照以下流程建立索引:

  1. Analyzer(文本分析器)

    Analyzer 会处理输入文本:

    1. 将文本切分为独立的单词或词项(Token);
    2. 根据配置执行相应的过滤操作。

    通过这一过程,Milvus 可以基于这些 Token 构建索引。

  2. Indexing(索引构建)

    完成文本分析后,Milvus 会创建一个倒排索引:

    将每个唯一 Token 映射到包含该 Token 的文档。

    例如:

    文本:

    复制代码
    Milvus is a vector database

    经过分析后:

    复制代码
    Milvus
    vector
    database

    建立倒排关系:

    复制代码
    Milvus    -> 文档1、文档5
    vector    -> 文档1、文档3
    database  -> 文档1、文档8

当用户执行文本匹配时,Milvus 会利用倒排索引快速查找包含目标词项的所有文档。

相比逐个扫描每篇文档进行匹配,倒排索引能够显著提高查询效率。

文本匹配作用于 VARCHAR 字段类型。在 Milvus 中,VARCHAR 本质上就是字符串类型。

要启用文本匹配,需要:

  • 设置 enable_analyzer=True
  • 设置 enable_match=True

并且可以在定义 Collection Schema 时,选择性配置文本分析器(Analyzer)

如果希望某个 VARCHAR 字段支持文本匹配,需要在定义字段 Schema 时,将:

python 复制代码
enable_analyzer=True

和:

python 复制代码
enable_match=True

同时设置为:

python 复制代码
True

这样 Milvus 会:

  • 对文本进行分词处理;
  • 为该字段创建倒排索引;
  • 支持快速、高效的文本匹配查询。

示例:

python 复制代码
from pymilvus import MilvusClient, DataType

schema = MilvusClient.create_schema(enable_dynamic_field=False)

schema.add_field(
    field_name="id",
    datatype=DataType.INT64,
    is_primary=True,
    auto_id=True
)

schema.add_field(
    field_name='text',
    datatype=DataType.VARCHAR,
    max_length=1000,
    enable_analyzer=True,  # 是否启用文本分析
    enable_match=True      # 是否启用文本匹配
)

schema.add_field(
    field_name="embeddings",
    datatype=DataType.FLOAT_VECTOR,
    dim=5
)

可选:配置 Analyzer(Optional: Configure an Analyzer)

关键词匹配的性能和准确性取决于所选择的 Analyzer。

不同 Analyzer 针对不同语言和文本结构进行了优化,因此选择合适的 Analyzer 会显著影响特定场景下的搜索效果。

默认情况下,Milvus 使用:

复制代码
standard analyzer

它会:

  • 根据空格和标点符号切分文本;
  • 删除长度超过 40 个字符的 Token;
  • 将文本转换为小写。

使用默认配置时,无需额外参数。

如果需要使用其他 Analyzer,可以通过:

python 复制代码
analyzer_params

参数进行配置。

例如,为英文文本配置:

python 复制代码
analyzer_params = {
    "type": "english"
}

schema.add_field(
    field_name='text',
    datatype=DataType.VARCHAR,
    max_length=200,
    enable_analyzer=True,
    analyzer_params=analyzer_params,
    enable_match=True,
)

Milvus 还提供了多种适用于不同语言和场景的 Analyzer。

当你已经在 Collection Schema 中为某个 VARCHAR 字段启用了文本匹配(Text Match)后,就可以使用 TEXT_MATCH 表达式执行文本匹配查询。

TEXT_MATCH 表达式用于指定需要搜索的字段以及目标查询词。

语法如下:

sql 复制代码
TEXT_MATCH(field_name, text)

参数说明:

参数 说明
field_name 需要搜索的 VARCHAR 字段名称。
text 需要搜索的词项(Term)。多个词项可以使用空格或其他适合当前语言以及 Analyzer 配置的分隔符进行分隔。

默认情况下,TEXT_MATCH 使用 OR 匹配逻辑

也就是说:

只要文档中包含指定词语中的任意一个,就会被返回。

例如希望搜索 text 字段中包含:

  • machine
  • deep

的文档,可以使用:

python 复制代码
filter = "TEXT_MATCH(text, 'machine deep')"

等价于:

text 复制代码
包含 machine
或者
包含 deep

你也可以通过逻辑运算符组合多个 TEXT_MATCH 表达式,实现 AND 匹配逻辑

例如查询 text 字段中同时包含:

  • machine
  • deep

的文档:

python 复制代码
filter = "TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')"

如果希望查询:

  • 包含 machine
  • 包含 learning
  • 但不包含 deep

的文档,可以使用:

python 复制代码
filter = "not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')"

文本匹配可以与**向量相似度搜索(Vector Similarity Search)**结合使用,用于缩小搜索范围并提升查询性能。

执行向量搜索之前,先通过文本匹配过滤 Collection:

text 复制代码
Text Match 过滤
        ↓
缩小候选文档范围
        ↓
Vector Similarity Search

这样可以减少需要进行向量计算的文档数量,从而降低查询耗时,提高搜索效率。

下面示例中:

过滤表达式会筛选出包含:

  • keyword1
  • keyword2

的文档。然后,仅在这些过滤后的文档集合中执行向量相似度搜索。

如果希望在搜索结果中高亮匹配词,可以配置 Text Highlighter(文本高亮器)

python 复制代码
# 匹配包含 keyword1 或 keyword2 的实体
filter = "TEXT_MATCH(text, 'keyword1 keyword2')"


# 假设:
# embeddings 是向量字段
# text 是 VARCHAR 字段

result = client.search(
    collection_name="my_collection",  # Collection 名称
    anns_field="embeddings",          # 向量字段名称
    data=[query_vector],              # 查询向量
    filter=filter,
    search_params={
        "params": {
            "nprobe": 10
        }
    },
    limit=10,                         # 最大返回结果数量
    output_fields=["id", "text"]      # 返回字段
)

文本匹配也可以用于普通标量查询(Scalar Filtering)。

只需要在 query() 方法的 expr 参数中指定 TEXT_MATCH 表达式,即可查询包含指定词语的文档。

下面示例会查询text 字段同时包含:

  • keyword1
  • keyword2

的文档。

python 复制代码
# 匹配同时包含 keyword1 和 keyword2 的实体
filter = "TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')"

result = client.query(
    collection_name="my_collection",
    filter=filter,
    output_fields=["id", "text"]
)

注意事项(Considerations)

  1. 存储空间影响

    启用字段的文本匹配功能后,Milvus 会为该字段创建倒排索引(Inverted Index)

    该索引会额外占用存储资源。

    因此,在决定是否启用该功能时,需要考虑存储成本。

    实际占用空间取决于:

    • 文本长度;
    • 唯一 Token 数量;
    • 使用的 Analyzer 类型。
  2. Analyzer 配置不可修改

    一旦在 Schema 中定义 Analyzer,其配置会永久绑定到该 Collection。

    如果后续发现需要使用其他 Analyzer:

    • 可以删除现有 Collection;
    • 使用新的 Analyzer 配置重新创建 Collection。
  3. Filter 表达式中的转义规则(Escape Rules in Filter Expressions)

    在表达式中:

    • 使用双引号 " 或单引号 ' 包裹的内容,会被解析为字符串常量。
    • 如果字符串常量中包含特殊字符,则必须使用转义序列表示。

    例如:

    字符 转义方式
    \ \\
    Tab 制表符 \t
    换行符 \n

    如果字符串使用单引号包裹:

    python 复制代码
    'text'

    那么:

    • 内部单引号必须转义:
    text 复制代码
    \'
    • 双引号可以直接使用,也可以转义:
    text 复制代码
    "

    或:

    text 复制代码
    \"

    示例:

    text 复制代码
    'It\'s milvus'

    如果字符串使用双引号包裹:

    python 复制代码
    "text"

    那么:

    • 内部双引号必须转义:
    text 复制代码
    \"
    • 单引号可以直接使用,也可以转义:
    text 复制代码
    '

    或:

    text 复制代码
    \'

    示例:

    text 复制代码
    "He said \"Hi\""

Milvus 中的 Text Match 本质上是:

text 复制代码
VARCHAR文本
        |
        ↓
Analyzer分词
        |
        ↓
Tantivy倒排索引
        |
        ↓
TEXT_MATCH查询
        |
        ↓
返回包含指定关键词的文档

与前面的 Full Text Search 区别:

功能 Text Match Full Text Search
核心技术 倒排索引 BM25 + 稀疏向量
是否评分 ❌ 不评分 ✅ BM25相关性评分
匹配方式 关键词是否存在 关键词权重排序
适合场景 过滤条件、精确匹配 搜索、RAG检索
类似产品 数据库 LIKE / ES term query ES BM25 search

简单理解:

Text Match = "有没有这个词?"
Full Text Search = "哪些文档和这个词最相关?"

文本高亮(Text Highlighter)

Milvus 中的 Highlighter(高亮器) 可以通过在文本字段中的匹配词两侧添加可自定义的标签(Tag),对匹配内容进行标注。

文本高亮可以:

  • 帮助用户理解文档为什么匹配查询;
  • 提升搜索结果的可读性;
  • 支持搜索和 RAG 应用中的富文本展示。

需要注意的是:

高亮处理是在最终搜索结果集上的后处理步骤(Post-processing)

不会影响

  • 候选文档召回(Candidate Retrieval);
  • 过滤逻辑(Filtering Logic);
  • 排序(Ranking);
  • 评分(Scoring)。

也就是说:

text 复制代码
搜索流程:

数据
 |
召回
 |
过滤
 |
排序
 |
评分
 |
结果集
 |
 ↓
Highlighter
 |
返回高亮结果

Highlighter 只负责对已经确定返回的结果进行文本标注。

Highlighter 提供三个独立的控制维度

  1. 高亮哪些词(Which terms are highlighted)

    你可以选择高亮词的来源。

    例如:

    • 高亮 BM25 全文检索中使用的搜索词;
    • 高亮文本过滤表达式(例如 TEXT_MATCH 条件)中的查询词。
  2. 如何渲染高亮词(How highlighted terms are rendered)

    你可以控制匹配词在输出结果中的展示方式。

    具体来说,可以配置插入到匹配词前后的标签。

    例如:

    普通标记:

    text 复制代码
    {term}

    HTML 标签:

    html 复制代码
    <em>term</em>

    用于实现更加丰富的前端展示效果。

  3. 如何返回高亮文本(How highlighted text is returned)

    你可以控制高亮结果以文本片段(Fragment)的形式返回,包括:

    • 片段从哪里开始;
    • 每个片段长度;
    • 返回多少个片段。

BM25 全文检索中的搜索词高亮(Search Term Highlighting in BM25 Full Text Search)

当执行 BM25 全文检索时,可以对返回结果中的搜索词进行高亮,从而帮助用户理解:

为什么该文档匹配当前查询。

在这种场景下高亮词直接来源于 BM25 全文检索中的查询词。Highlighter 会使用这些查询词,对最终结果中的匹配文本进行标注。

假设文本字段中存储以下内容:

text 复制代码
Milvus supports full text search. Use BM25 for keyword relevance. Filters can narrow results.

如果希望在 BM25 全文检索中高亮搜索词,需要创建 LexicalHighlighter,并开启 BM25 搜索词高亮:

python 复制代码
from pymilvus import LexicalHighlighter

highlighter = LexicalHighlighter(
    pre_tags=["{"],              # 插入到每个高亮词之前的标签
    post_tags=["}"],             # 插入到每个高亮词之后的标签
    highlight_search_text=True   # 开启 BM25 全文检索搜索词高亮
)

在这个示例中pre_tagspost_tags用于控制高亮文本在输出结果中的展示方式。

当前配置:

python 复制代码
pre_tags=["{"]
post_tags=["}"]

因此匹配词会被包裹成:

text 复制代码
{term}

例如:

text 复制代码
{BM25}

也可以传入多个标签例如:

python 复制代码
["<b>", "<i>"]

当多个词需要高亮时:

  • 标签会按照列表顺序应用;
  • 根据匹配词出现顺序循环使用。

highlight_search_text=True

表示:

告诉 Milvus 使用 BM25 全文检索中的搜索词作为高亮词来源。

创建 Highlighter 对象后,需要将配置传递给 BM25 全文检索请求:

python 复制代码
results = client.search(
    ...,
    data=["BM25"],              # BM25 全文检索中的搜索词
    highlighter=highlighter     # 传入高亮配置
)

启用高亮后,Milvus 会在返回结果中增加一个专门的:

text 复制代码
highlight

字段,默认情况下:

  • 高亮结果会以文本片段(Fragment)的形式返回;
  • 片段默认从第一个匹配词的位置开始。

例如搜索词:

text 复制代码
BM25

返回结果:

json 复制代码
{
    "...": "...",
    "highlight": {
        "text": [
            "{BM25} for keyword relevance. Filters can narrow results."
        ]
    }
}

可以看到原文本:

text 复制代码
Use BM25 for keyword relevance.

返回时:

text 复制代码
Use {BM25} for keyword relevance.

如果需要控制:

  • 返回片段的位置;
  • 片段长度;
  • 返回片段数量;

请参考:

Return highlighted text as fragments(将高亮文本作为片段返回)

过滤条件中的查询词高亮(Query Term Highlighting in Filtering)

除了高亮搜索词外,Milvus 还支持高亮文本过滤表达式中的查询词

目前,查询词高亮仅支持:

text 复制代码
TEXT_MATCH

过滤条件。

更多信息请参考 Text Match

在该场景中:

  • 高亮词来自文本过滤表达式;
  • 过滤条件决定哪些文档匹配;
  • Highlighter 负责标注匹配的文本片段。

假设文本字段内容:

text 复制代码
This document explains how text filtering works in Milvus.

要高亮过滤条件中的查询词,需要创建 LexicalHighlighter,并定义与过滤条件对应的 highlight_query

python 复制代码
from pymilvus import LexicalHighlighter

highlighter = LexicalHighlighter(
    pre_tags=["{"],              # 高亮词前的标签
    post_tags=["}"],             # 高亮词后的标签
    highlight_query=[{
        "type": "TextMatch",     # 文本过滤类型
        "field": "text",         # 目标文本字段
        "text": "text filtering" # 需要高亮的词
    }]
)

配置说明:

  • pre_tags / post_tags

    控制高亮文本格式。

    示例:

text 复制代码
{term}

也可以传入多个标签:

python 复制代码
["<b>", "<i>"]

多个词高亮时,会按照列表顺序循环使用标签。

  • highlight_query

    指定需要高亮的过滤查询词。

创建 Highlighter 后,在搜索请求中同时传入相同的过滤表达式和高亮配置:

python 复制代码
results = client.search(
    ...,
    filter='TEXT_MATCH(text, "text filtering")',
    highlighter=highlighter
)

启用过滤查询词高亮后,Milvus 会在返回结果中增加:

json 复制代码
highlight

字段。默认情况下,高亮结果以片段形式返回,并从第一个匹配词开始。

例如第一个匹配词:

text 复制代码
text

返回:

json 复制代码
{
    "highlight": {
        "text": [
            "{text} {filtering} works in Milvus."
        ]
    }
}

如果需要控制高亮片段的位置、长度和数量,请参考:

Return highlighted text as fragments

基于片段的高亮输出(Fragment-based Highlighting Output)

默认情况下,Milvus 会从第一个匹配词开始返回高亮文本片段。

片段相关配置可以进一步控制:

  • 返回片段的位置;
  • 片段长度;
  • 返回片段数量。

但不会改变高亮词的范围。

假设文本字段内容:

text 复制代码
Milvus supports full text search. Use BM25 for keyword relevance. Filters can narrow results.

通过 LexicalHighlighter 配置片段参数:

python 复制代码
from pymilvus import LexicalHighlighter

highlighter = LexicalHighlighter(
    pre_tags=["{"],
    post_tags=["}"],
    highlight_search_text=True,
    fragment_offset=5,      # 第一个匹配词前保留的字符数量
    fragment_size=60,       # 每个片段最大长度
    num_of_fragments=1      # 最大返回片段数量
)

参数说明:

参数 说明
fragment_offset 在第一个高亮词之前保留上下文字符数量。
fragment_size 限制每个返回片段的最大长度。
num_of_fragments 控制返回的片段数量。

创建 Highlighter 后,将配置应用到搜索请求:

python 复制代码
results = client.search(
    ...,
    data=["BM25"],
    highlighter=highlighter
)

启用片段高亮后,Milvus 会在 highlight 字段中返回文本片段:

json 复制代码
{
    "highlight": {
        "text": [
            "Use {BM25} for keyword relevance. Filters can narrow results."
        ]
    }
}

输出说明:

  • 片段不会严格从 {BM25} 开始,因为设置了:
text 复制代码
fragment_offset
  • 只返回一个片段,因为:
text 复制代码
num_of_fragments=1
  • 片段长度受到:
text 复制代码
fragment_size

限制。

相关推荐
张3238 小时前
Go语言基础 Map 函数值 闭包
开发语言·golang
北冥you鱼9 小时前
Go Modules 使用指南:从入门到精通
开发语言·后端·golang
灯澜忆梦10 小时前
【dp_1】爬楼梯 | 斐波那契数 | 第 N 个泰波那契数 | 三步问题
算法·golang
geovindu10 小时前
go: Floyd-Warshall Algorithms
开发语言·后端·算法·golang
记忆停留w21 小时前
从单体到微服务:Redis 协同 MySQL、Milvus、MinIO 搭建企业级RAG/AI Agent脚手架架构
大数据·人工智能·redis·微服务·ai·架构·milvus
小二·21 小时前
RAG + 向量数据库实战:ChromaDB / Milvus / FAISS 选型与性能横评
数据库·milvus·faiss
大侠锅锅1 天前
第 9 篇:状态机实践——状态环 + 步进表替代 if-else 地狱
golang·边缘计算·状态机
灯澜忆梦1 天前
iota枚举
golang
进击的程序猿~1 天前
Go Slice源码深度解析指南
开发语言·后端·golang