如何使用 Milvus 混合空间和向量搜索

  • Engineering
March 18, 2026
Alden

像 "查找 3 公里内的浪漫餐厅 "这样的查询听起来很简单。其实不然,因为它结合了位置过滤和语义搜索。大多数系统都需要在两个数据库中分割这个查询,这意味着同步数据、在代码中合并结果以及额外的延迟。

Milvus2.6.4 消除了这种分割。通过本地GEOMETRY数据类型和R-Tree索引,Milvus 可以在单个查询中同时应用位置和语义约束。这使得混合空间和语义搜索变得更容易、更高效。

本文将解释为什么需要这一改变,GEOMETRY 和 R-Tree 在 Milvus 中是如何工作的,预期会有哪些性能提升,以及如何使用 Python SDK 进行设置。

像 "3 公里内的浪漫餐厅 "这样的查询很难处理,原因有两个:

  • "浪漫 "需要语义搜索。系统必须将餐厅评论和标签向量化,然后在 Embeddings 空间中通过相似度查找匹配。这只适用于向量数据库。
  • "3公里以内 "需要空间过滤。搜索结果必须限制在 "用户周围 3 公里内",有时甚至是 "特定的交付多边形或行政边界内"。

在传统架构中,满足这两种需求通常意味着并行运行两个系统:

  • PostGIS / Elasticsearch用于地理围栏、距离计算和空间过滤。
  • 向量数据库,用于在 Embeddings 上进行近似近邻(ANN)搜索。

这种 "双数据库 "设计产生了三个实际问题:

  • 痛苦的数据同步。如果一家餐厅更改了地址,就必须同时更新地理系统和向量数据库。缺少一次更新就会产生不一致的结果。
  • 延迟更长。应用程序必须调用两个系统并合并它们的输出,从而增加了网络往返和处理时间。
  • 过滤效率低。如果系统先运行向量搜索,往往会返回许多离用户很远的结果,之后不得不丢弃。如果先应用位置过滤,剩余的结果集仍然很大,因此向量搜索步骤的成本仍然很高。

Milvus 2.6.4 通过在向量数据库中直接添加空间几何支持解决了这个问题。现在,语义搜索和位置过滤在同一个查询中运行。由于所有功能都在一个系统中,混合搜索变得更快、更易于管理。

GEOMETRY 为 Milvus 带来的新功能

Milvus 2.6 引入了一种名为 DataType.GEOMETRY 的标量字段类型。Milvus 现在不再以单独的经度和纬度数字来存储位置,而是存储几何对象:点、线和多边形。像 "这个点在某个区域内吗?"或 "它在 X 米以内吗?"这样的查询就变成了本地操作符。无需在原始坐标上建立变通方法。

它的实现遵循OpenGIS 简单地物访问标准,因此能与大多数现有的地理空间工具配合使用。几何数据使用WKT(Well-Known 文本)存储和查询,这是一种标准文本格式,人类可读,程序可解析。

支持的几何类型

  • :单一位置,如商店地址或车辆的实时位置
  • 线段:一条线,如道路中心线或移动路径
  • 多边形:区域,如行政边界或地理围栏
  • 收集类型:多点(MULTIPOINT)、多线跟踪(MULTILINESTRING)、多多边形(MULTIPOLYGON)和几何集合(GEOMETRYCOLLECTION)。

它还支持标准空间操作符,包括

  • 空间关系:包含(ST_CONTAINS、ST_WITHIN)、交叉(ST_INTERSECTS、ST_CROSSES)和接触(ST_TOUCHES)
  • 距离操作:计算几何体之间的距离(ST_DISTANCE)和过滤给定距离内的对象(ST_DWITHIN)

R 树索引如何在 Milvus 内部工作

Milvus查询引擎内置了几何支持,而不仅仅是作为应用程序接口(API)功能。ISpatial 数据直接在引擎内部使用 R-Tree(矩形树)索引进行索引和处理。

R-Tree使用最小边界矩形(MBR)对附近的对象进行分组。在查询过程中,引擎会跳过与查询几何图形不重叠的大区域,只对一小部分候选对象进行详细检查。这比扫描每个对象要快得多。

Milvus 如何构建 R 树

R 树的构建是分层进行的:

层次Milvus 的作用直观类比
树叶层对于每个几何对象(点、线或多边形),Milvus 都会计算其最小边界矩形 (MBR),并将其存储为叶节点。将每个对象包裹在一个完全适合它的透明框中。
中间层将附近的叶节点分组(通常每次 50-100 个),并创建一个更大的父级 MBR 以覆盖所有叶节点。将同一小区的包裹装入一个快递箱。
根级这种分组一直向上进行,直到单个根 MBR 覆盖所有数据。将所有箱子装上一辆长途卡车。

有了这种结构,空间查询的复杂度就会从完整扫描的O(n)降到O(log n )。在实践中,对数百万条记录的查询可以从数百毫秒缩短到几毫秒,而不会降低准确性。

查询如何执行:两阶段过滤

为了兼顾速度和正确性,Milvus 采用了两阶段过滤策略:

  • 粗略过滤:R 树索引首先检查查询的边界矩形是否与索引中的其他边界矩形重叠。这样可以快速删除大部分不相关的数据,只保留一小部分候选数据。由于这些矩形形状简单,因此检查速度非常快,但可能会包含一些实际上不匹配的结果。
  • 精细过滤:然后使用GEOS(与 PostGIS 等系统使用的几何库相同)检查剩余的候选结果。GEOS 会进行精确的几何计算,如形状是否相交或一个包含另一个,以生成正确的最终结果。

Milvus 接受WKT(Well-Known Text)格式的几何数据,但内部存储为WKB(Well-Known Binary)格式WKB 格式更紧凑,可减少存储空间并改善 I/O。GEOMETRY 字段还支持内存映射(mmap)存储,因此大型空间数据集不必完全放在 RAM 中。

使用 R-Tree 提高性能

查询延迟随数据增长保持不变。

在没有 R-Tree 索引的情况下,查询时间与数据大小呈线性关系--数据量增加 10 倍,查询速度大约降低 10 倍。

有了 R-Tree 索引,查询时间则呈对数增长。在拥有数百万条记录的数据集上,空间过滤比全面扫描快几十到几百倍。

不为速度牺牲准确性

R-Tree 通过边界框缩小候选对象的范围,然后 GEOS 用精确的几何数学检查每一个候选对象。任何看似匹配但实际上不在查询区域内的数据都会在第二次扫描中被删除。

提高混合搜索吞吐量

R-Tree 会首先删除目标区域外的记录。然后,Milvus 只对剩余的候选对象运行向量相似性(L2、IP 或余弦)。更少的候选记录意味着更低的搜索成本和更高的每秒查询次数(QPS)。

入门:使用 Python SDK 进行几何分析

定义 Collections 并创建索引

首先,在 Collections Schema 中定义 DataType.GEOMETRY 字段。这样,Milvus 就能存储和查询几何数据。

from pymilvus import MilvusClient, DataType  
import numpy as np  
# Connect to Milvus  
milvus_client = MilvusClient("[http://localhost:19530](http://localhost:19530)")  
collection_name = "lb_service_demo"  
dim = 128  
# 1. Define schema  
schema = milvus_client.create_schema(enable_dynamic_field=True)  
schema.add_field("id", DataType.INT64, is_primary=True)  
schema.add_field("vector", DataType.FLOAT_VECTOR, dim=dim)  
schema.add_field("location", DataType.GEOMETRY)  # Define geometry field  
schema.add_field("poi_name", DataType.VARCHAR, max_length=128)  
# 2. Create index parameters  
index_params = milvus_client.prepare_index_params()  
# Create an index for the vector field (e.g., IVF_FLAT)  
index_params.add_index(  
   field_name="vector",  
   index_type="IVF_FLAT",  
   metric_type="L2",  
   params={"nlist": 128}  
)  
# Create an R-Tree index for the geometry field (key step)  
index_params.add_index(  
   field_name="location",  
   index_type="RTREE"  # Specify the index type as RTREE  
)  
# 3. Create collection  
if milvus_client.has_collection(collection_name):  
   milvus_client.drop_collection(collection_name)  
milvus_client.create_collection(  
   collection_name=collection_name,  
   schema=schema,  
   index_params=index_params,  # Create the collection with indexes attached  
   consistency_level="Strong"  
)  
print(f"Collection {collection_name} created with R-Tree index.")  

插入数据

插入数据时,几何值必须是 WKT(Well-Known 文本)格式。每条记录包括几何体、向量和其他字段。

# Mock data: random POIs in a region of Beijing  
data = []  
# Example WKT: POINT(longitude latitude)  
geo_points = [  
   "POINT(116.4074 39.9042)",  # Near the Forbidden City  
   "POINT(116.4600 39.9140)",  # Near Guomao  
   "POINT(116.3200 39.9900)",  # Near Tsinghua University  
]  
for i, wkt in enumerate(geo_points):  
   vec = np.random.random(dim).tolist()  
   data.append({  
       "id": i,  
       "vector": vec,  
       "location": wkt,  
       "poi_name": f"POI_{i}"  
   })  
res = milvus_client.insert(collection_name=collection_name, data=data)  
print(f"Inserted {res['insert_count']} entities.")  

运行空间-向量混合查询(示例)

场景:查找在向量空间中最相似且位于给定点(如用户所在位置)2 公里范围内的前 3 个 POI。

使用 ST_DWITHIN 操作符应用距离筛选器。距离值以为单位。

# Load the collection into memory  
milvus_client.load_collection(collection_name)  
# User location (WKT)  
user_loc_wkt = "POINT(116.4070 39.9040)"  
search_vec = np.random.random(dim).tolist()  
# Build the filter expression: use ST_DWITHIN for a 2000-meter radius filter  
filter_expr = f"ST_DWITHIN(location, '{user_loc_wkt}', 2000)"  
# Execute the search  
search_res = milvus_client.search(  
   collection_name=collection_name,  
   data=[search_vec],  
   filter=filter_expr,  # Inject geometry filter  
   limit=3,  
   output_fields=["poi_name", "location"]  
)  
print("Search Results:")  
for hits in search_res:  
   for hit in hits:  
       print(f"ID: {hit['id']}, Score: {hit['distance']:.4f}, Name: {hit['entity']['poi_name']}")  

生产使用提示

  • 始终为 GEOMETRY 字段创建 R-Tree 索引。对于超过 10,000 个实体的数据集,没有 RTREE 索引的空间筛选器会退回到完全扫描,性能会急剧下降。
  • 使用一致的坐标系。所有位置数据必须使用相同的系统(如 WGS84)。混合坐标系会破坏距离和包含计算。
  • 为查询选择正确的空间操作符。ST_DWITHIN 用于 "X 米以内 "的搜索。ST_CONTAINS 或 ST_WITHIN 用于地理围栏和包含检查。
  • 自动处理 NULL 几何值。如果 GEOMETRY 字段为空值(nullable=True),Milvus 会在空间查询时跳过 NULL 值。无需额外的过滤逻辑。

部署要求

要在生产中使用这些功能,请确保您的环境满足以下要求。

1.Milvus 版本

必须运行Milvus 2.6.4 或更高版本。早期版本不支持 DataType.GEOMETRY 或RTREE索引类型。

2.SDK 版本

  • PyMilvus:升级到最新版本(推荐2.6.x系列)。这是正确 WKT 序列化和传递 RTREE 索引参数所必需的。
  • Java / Go / Node SDK:检查每个 SDK 的发布说明,确认它们与2.6.4proto 定义一致。

3.内置几何库

Milvus 服务器已包含 Boost.Geometry 和 GEOS,因此无需自行安装这些库。

4.内存使用和容量规划

R-Tree 索引会占用额外内存。在规划容量时,请记住为几何索引以及 HNSW 或 IVF 等向量索引做好预算。几何字段支持内存映射(mmap)存储,可以通过在磁盘上保留部分数据来减少内存使用量。

结论

基于位置的语义搜索需要的不仅仅是在向量查询中加入地理过滤器。它需要内置的空间数据类型、适当的索引以及能同时处理位置和向量的查询引擎。

Milvus 2.6.4通过本地GEOMETRY字段和R-Tree索引解决了这一问题。空间过滤和向量搜索在单个查询中针对单个数据存储运行。R-Tree 可处理快速的空间剪枝,而 GEOS 则可确保精确的结果。

对于需要位置感知检索的应用程序来说,这消除了运行和同步两个独立系统的复杂性。

如果您正在进行位置感知或混合空间和向量搜索,我们很乐意听取您的经验。

对 Milvus 有疑问?加入我们的Slack 频道,或预约 20 分钟的Milvus Office Hours会议。

    Try Managed Milvus for Free

    Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

    Get Started

    Like the article? Spread the word

    扩展阅读