跳到主要内容

向量查询

在 AI 和机器学习场景中,文本、图片、音频等非结构化数据通常会被模型编码为高维向量(也称 Embedding)。
向量之间的距离可以衡量原始数据的语义相似度 —— 距离越近,语义越相似。

原始数据🐱 猫的照片🐶 狗的照片🚗 汽车照片📝 一段文字Embedding模型编码向量空间[0.12, 0.85, ..., 0.33][0.15, 0.80, ..., 0.31][0.91, 0.02, ..., 0.77][0.45, 0.62, ..., 0.19]距离计算相似度排序查询结果🥇 猫照 (dist=0.05)🥈 狗照 (dist=0.12)🥉 文字 (dist=0.58)④ 汽车 (dist=0.91)

向量查询的核心问题是:给定一个目标向量,在数据库中找到与它距离最近的记录

距离度量

不同的度量方式适用于不同的场景,dbVisitor 支持 6 种度量:

L2 欧氏距离空间中两点的直线距离d = √Σ(aᵢ - bᵢ)²值越小 → 越相似✅ 通用场景首选pgvector: <->Cosine 余弦距离两向量夹角的余弦补值d = 1 - cos(θ)值越小 → 方向越接近✅ 文本语义/NLPpgvector: <=>IP 内积向量的点积(负值排序)d = -Σ(aᵢ × bᵢ)值越小 → 内积越大✅ 推荐系统pgvector: <#>Hamming 距离不同位的个数适用于二值向量值越小 → 越相似✅ 哈希 / 指纹匹配pgvector: <~>Jaccard 距离集合交并比的补值d = 1 - |A∩B| / |A∪B|值越小 → 越相似✅ 集合 / 标签相似pgvector: <%>BM25基于词频的文本相关度经典信息检索评分值越小 → 越相关✅ 全文检索pgvector: <?>
如何选择度量方式
  • 不确定时选 L2(欧氏距离),它是最通用的度量方式。
  • 文本语义搜索选 Cosine,它只关注方向不关注向量长度,适合归一化后的 Embedding。
  • 推荐/排序场景选 IP(内积),当向量已归一化时,IP 结果等价于 Cosine 相似度。

两种查询模式

dbVisitor 提供两种向量搜索模式,分别对应 SQL 中的 ORDER BYWHERE

KNN 排序模式 (orderBy*)返回距离最近的 K 条记录target按距离排序,取前 K 个(蓝色)SQL: ORDER BY embedding <-> ? LIMIT KRange 过滤模式 (vectorBy*)返回距离小于阈值的所有记录thresholdtarget圈内全部返回(绿色),圈外丢弃(灰色)SQL: WHERE embedding <-> ? < threshold
对比项KNN (orderBy*)Range (vectorBy*)
SQL 位置ORDER BYWHERE
返回数量固定 K 条(需配合 initPage不固定,取决于阈值
适用场景"找最相似的 N 个""找所有距离在范围内的"
可组合性可追加 WHERE 条件做预过滤可与其他 WHERE 条件自由组合

准备工作

1. 建表

以 PostgreSQL + pgvector 为例,需要安装 vector 扩展并创建向量列:

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE product_vector (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
embedding vector(128) -- 128 维向量
);

2. 实体映射

向量实体类
@Table("product_vector")
public class ProductVector {
@Column(primary = true)
private Integer id;
private String name;

@Column(typeHandler = PgVectorTypeHandler.class)
private List<Float> embedding;

// getter / setter ...
}
  • 向量字段使用 List<Float> 表示。
  • 需要为向量字段指定 typeHandler 来处理 List<Float> 与数据库向量类型之间的转换。
提示

PgVectorTypeHandler 是针对 PostgreSQL pgvector 的实现,利用 PGobject 进行 List<Float>vector 类型的互转。 如果使用其他数据库(如 Milvus),需要使用对应的 TypeHandler。

3. 向量参数格式

在 KNN 排序查询中(orderBy* 系列),向量参数需要传递数据库能识别的类型
以 pgvector 为例,不能直接传递 List<Float>,需要包装为 PGobject

构造向量查询参数(pgvector)
PGobject vectorParam = new PGobject();
vectorParam.setType("vector");
vectorParam.setValue("[0.1,0.2,0.3,...]"); // pgvector 文本格式

在 Range 过滤查询中(vectorBy* 系列),向量参数会经过实体映射的 TypeHandler 自动转换,因此可以直接传 List<Float>

KNN 排序查询

使用 orderBy* 方法按向量距离进行排序,返回距离目标最近的 K 条记录。

L2 欧氏距离

按 L2 距离排序
LambdaTemplate lambda = ...
Object target = ...; // 目标向量(PGobject 或数据库对应类型)

List<ProductVector> results = lambda.query(ProductVector.class)
.orderByL2(ProductVector::getEmbedding, target)
.queryForList();

// 对应的 SQL(pgvector):
// SELECT * FROM product_vector ORDER BY embedding <-> ? ASC

Cosine 余弦距离

按 Cosine 距离排序
List<ProductVector> results = lambda.query(ProductVector.class)
.orderByCosine(ProductVector::getEmbedding, target)
.queryForList();

// 对应的 SQL(pgvector):
// SELECT * FROM product_vector ORDER BY embedding <=> ? ASC

IP 内积距离

按 Inner Product 距离排序
List<ProductVector> results = lambda.query(ProductVector.class)
.orderByIP(ProductVector::getEmbedding, target)
.queryForList();

// 对应的 SQL(pgvector):
// SELECT * FROM product_vector ORDER BY embedding <#> ? ASC
IP 距离说明

pgvector 的 <#> 运算符返回负内积。排序后,内积最大(最相似)的记录排在最前面。

Top-K 查询

配合 initPage 实现只返回最近的 K 条记录:

Top-K 近邻
int topK = 5;

List<ProductVector> results = lambda.query(ProductVector.class)
.orderByL2(ProductVector::getEmbedding, target)
.initPage(topK, 0) // 只取前 5 条
.queryForList();

通用度量接口

通过 orderByMetric 方法可以使用 MetricType 枚举动态指定度量方式:

使用 MetricType 枚举
import net.hasor.dbvisitor.lambda.core.MetricType;

List<ProductVector> results = lambda.query(ProductVector.class)
.orderByMetric(MetricType.L2, ProductVector::getEmbedding, target)
.queryForList();

全部可用的度量方式参见上方 距离度量 章节。

MetricType快捷方法pgvector 运算符
MetricType.L2orderByL2<->
MetricType.COSINEorderByCosine<=>
MetricType.IPorderByIP<#>
MetricType.HAMMINGorderByHamming<~>
MetricType.JACCARDorderByJaccard<%>
MetricType.BM25orderByBM25<?>

Range 范围过滤

使用 vectorBy* 方法只返回到目标向量距离小于阈值(threshold)的记录。它属于 WHERE 条件,可与其他条件自由组合。

L2 距离过滤

L2 距离范围过滤
List<Float> target = ...;   // 可直接使用 List<Float>
double threshold = 5.0;

List<ProductVector> results = lambda.query(ProductVector.class)
.vectorByL2(ProductVector::getEmbedding, target, threshold)
.queryForList();

// 对应的 SQL(pgvector):
// SELECT * FROM product_vector WHERE embedding <-> ? < ?

Cosine 距离过滤

Cosine 距离范围过滤
List<ProductVector> results = lambda.query(ProductVector.class)
.vectorByCosine(ProductVector::getEmbedding, target, 0.1)
.queryForList();

// 对应的 SQL(pgvector):
// SELECT * FROM product_vector WHERE embedding <=> ? < ?

IP 距离过滤

IP 距离范围过滤
List<ProductVector> results = lambda.query(ProductVector.class)
.vectorByIP(ProductVector::getEmbedding, target, -50.0)
.queryForList();
vectorBy 与 orderBy 参数差异

vectorBy* 的向量参数会经过实体映射的 TypeHandler 自动转换,因此可以直接传递 List<Float>
orderBy* 的向量参数直接进入 SQL 参数绑定,需要传递数据库能识别的类型(如 PGobject)。

条件开关

所有 vectorBy* 方法都支持通过第一个 boolean 参数控制条件是否生效:

动态控制向量过滤
boolean enableVectorFilter = ...;

List<ProductVector> results = lambda.query(ProductVector.class)
.vectorByL2(enableVectorFilter, ProductVector::getEmbedding, target, threshold)
.queryForList();

// enableVectorFilter = false 时,向量过滤条件不会出现在 SQL 中

组合查询

向量查询可以和标量条件自由组合,实现先过滤再排序先排序再过滤

KNN + 标量过滤

只在特定类别中做近邻搜索
List<ProductVector> results = lambda.query(ProductVector.class)
.likeRight(ProductVector::getName, "Cat-A") // 标量条件
.orderByL2(ProductVector::getEmbedding, target) // 向量排序
.initPage(3, 0) // Top-3
.queryForList();

// 对应的 SQL(pgvector):
// SELECT * FROM product_vector
// WHERE name LIKE 'Cat-A%'
// ORDER BY embedding <-> ?
// LIMIT 3

Range + 标量过滤

向量范围过滤 + 标量条件
List<ProductVector> results = lambda.query(ProductVector.class)
.likeRight(ProductVector::getName, "R-A") // 标量条件
.vectorByL2(ProductVector::getEmbedding, target, 6.0) // 向量范围过滤
.queryForList();

// 对应的 SQL(pgvector):
// SELECT * FROM product_vector
// WHERE name LIKE 'R-A%'
// AND embedding <-> ? < ?

基础操作

向量数据的增删改操作与普通实体完全一致,通过 TypeHandler 自动处理 List<Float> 的序列化和反序列化。

插入向量
ProductVector p = new ProductVector();
p.setId(1001);
p.setName("sample");
p.setEmbedding(Arrays.asList(0.1f, 0.2f, 0.3f, ...)); // 128 维

lambda.insert(ProductVector.class)
.applyEntity(p)
.executeSumResult();
更新向量
List<Float> newVec = Arrays.asList(0.9f, 0.8f, 0.7f, ...);

lambda.update(ProductVector.class)
.eq(ProductVector::getId, 1001)
.updateTo(ProductVector::getEmbedding, newVec)
.doUpdate();
读取向量
ProductVector loaded = lambda.query(ProductVector.class)
.eq(ProductVector::getId, 1001)
.queryForObject();

List<Float> vec = loaded.getEmbedding(); // 自动反序列化为 List<Float>

API 参考

KNN 排序(QueryFunc 接口)

方法说明
orderByL2(P property, Object vector)按 L2 距离排序
orderByCosine(P property, Object vector)按 Cosine 距离排序
orderByIP(P property, Object vector)按 IP 距离排序
orderByHamming(P property, Object vector)按 Hamming 距离排序
orderByJaccard(P property, Object vector)按 Jaccard 距离排序
orderByBM25(P property, Object vector)按 BM25 评分排序
orderByMetric(MetricType, P property, Object vector)通过枚举指定度量方式

Range 过滤(QueryCompare 接口)

方法说明
vectorByL2(P property, Object vector, Number threshold)L2 距离小于阈值
vectorByCosine(P property, Object vector, Number threshold)Cosine 距离小于阈值
vectorByIP(P property, Object vector, Number threshold)IP 距离小于阈值
vectorByHamming(P property, Object vector, Number threshold)Hamming 距离小于阈值
vectorByJaccard(P property, Object vector, Number threshold)Jaccard 距离小于阈值
vectorByBM25(P property, Object vector, Number threshold)BM25 距离小于阈值

所有 vectorBy* 方法均支持 (boolean test, P property, Object vector, Number threshold) 形式的重载,用于动态控制条件是否生效。

相关的类

  • net.hasor.dbvisitor.lambda.core.MetricType
  • net.hasor.dbvisitor.lambda.core.QueryFunc
  • net.hasor.dbvisitor.lambda.core.QueryCompare