向量数据库对比与推荐
引言
向量数据库是专门为存储和查询高维向量数据设计的数据库,广泛应用于机器学习和人工智能领域,如语义搜索、推荐系统和检索增强生成(RAG)。
1. Milvus
Milvus是一个开源的向量数据库,专为嵌入式向量搜索设计,同时提供Go和Python客户端。
优势:
高性能、可扩展
完善的Go和Python SDK
支持多种索引类型
活跃的社区支持
2. Qdrant
Qdrant是一个专注于向量相似性搜索的数据库,同样支持Go和Python。
优势:
过滤器和有效的数据管理
直观的API设计
容易部署和使用
开源且有商业支持
3. Weaviate
Weaviate是一个开源的向量搜索引擎,提供Go和Python客户端。
优势:
GraphQL和REST API
集成了多种ML模型
强大的模块化设计
支持多模态数据
4. Chroma
Chroma虽然主要以Python为主,但通过REST API也可以与Go配合使用。
优势:
极其简单的API
针对AI应用进行了优化
支持嵌入、存储和检索
开源
代码示例
Milvus - Python示例
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType, utility
import numpy as np
# 连接到Milvus
connections.connect(host="localhost", port="19530")
# 定义集合架构
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=128)
]
schema = CollectionSchema(fields=fields, description="文档向量集合")
# 创建集合
collection_name = "documents"
if utility.has_collection(collection_name):
utility.drop_collection(collection_name)
collection = Collection(name=collection_name, schema=schema)
# 创建索引
index_params = {
"index_type": "IVF_FLAT",
"metric_type": "L2",
"params": {"nlist": 128}
}
collection.create_index(field_name="embedding", index_params=index_params)
# 插入数据
data = [
[i for i in range(10)], # id
np.random.random((10, 128)).tolist() # embedding
]
collection.insert(data)
collection.flush()
# 加载集合
collection.load()
# 搜索
vectors_to_search = np.random.random((1, 128)).tolist()
search_params = {"metric_type": "L2", "params": {"nprobe": 10}}
results = collection.search(
vectors_to_search,
"embedding",
search_params,
limit=3,
output_fields=["id"]
)
print(results)Milvus - Golang示例
package main
import (
"context"
"fmt"
"log"
"math/rand"
"github.com/milvus-io/milvus-sdk-go/v2/client"
"github.com/milvus-io/milvus-sdk-go/v2/entity"
)
func main() {
// 连接到Milvus
ctx := context.Background()
c, err := client.NewGrpcClient(ctx, "localhost:19530")
if err != nil {
log.Fatal("连接失败:", err.Error())
}
defer c.Close()
// 定义集合
collectionName := "documents"
dim := int64(128)
// 检查并删除已存在的集合
has, err := c.HasCollection(ctx, collectionName)
if err != nil {
log.Fatal("检查集合错误:", err.Error())
}
if has {
err = c.DropCollection(ctx, collectionName)
if err != nil {
log.Fatal("删除集合错误:", err.Error())
}
}
// 创建集合
schema := &entity.Schema{
CollectionName: collectionName,
Description: "文档向量集合",
Fields: []*entity.Field{
{
Name: "id",
DataType: entity.FieldTypeInt64,
PrimaryKey: true,
AutoID: false,
},
{
Name: "embedding",
DataType: entity.FieldTypeFloatVector,
TypeParams: map[string]string{
"dim": fmt.Sprintf("%d", dim),
},
},
},
}
err = c.CreateCollection(ctx, schema, int64(1))
if err != nil {
log.Fatal("创建集合错误:", err.Error())
}
// 创建索引
idx, err := entity.NewIndexIvfFlat(entity.L2, 128)
if err != nil {
log.Fatal("创建索引错误:", err.Error())
}
err = c.CreateIndex(ctx, collectionName, "embedding", idx, false)
if err != nil {
log.Fatal("创建索引错误:", err.Error())
}
// 加载集合
err = c.LoadCollection(ctx, collectionName, false)
if err != nil {
log.Fatal("加载集合错误:", err.Error())
}
// 插入数据
idColumn := make([]int64, 10)
embeddingColumn := make([][]float32, 10)
for i := 0; i < 10; i++ {
idColumn[i] = int64(i)
vec := make([]float32, dim)
for j := 0; j < int(dim); j++ {
vec[j] = rand.Float32()
}
embeddingColumn[i] = vec
}
idColEntity := entity.NewColumnInt64("id", idColumn)
embeddingColEntity := entity.NewColumnFloatVector("embedding", int(dim), embeddingColumn)
_, err = c.Insert(ctx, collectionName, "", idColEntity, embeddingColEntity)
if err != nil {
log.Fatal("插入数据错误:", err.Error())
}
// 搜索
vectors := make([][]float32, 1)
vectors[0] = make([]float32, dim)
for i := 0; i < int(dim); i++ {
vectors[0][i] = rand.Float32()
}
sp, _ := entity.NewIndexIvfFlatSearchParam(10)
searchResult, err := c.Search(
ctx,
collectionName,
[]string{},
"",
[]string{"id"},
vectors,
"embedding",
entity.L2,
3,
sp,
)
if err != nil {
log.Fatal("搜索错误:", err.Error())
}
fmt.Printf("搜索结果: %v\n", searchResult)
}Qdrant - Python示例
from qdrant_client import QdrantClient
from qdrant_client.http import models
import numpy as np
# 连接到Qdrant
client = QdrantClient(host="localhost", port=6333)
# 创建集合
client.recreate_collection(
collection_name="documents",
vectors_config=models.VectorParams(
size=128,
distance=models.Distance.COSINE
)
)
# 上传向量
vectors = np.random.rand(10, 128).tolist()
payload = [{"text": f"文档 {i}"} for i in range(10)]
client.upsert(
collection_name="documents",
points=models.Batch(
ids=[i for i in range(10)],
vectors=vectors,
payloads=payload
)
)
# 搜索相似向量
search_vector = np.random.rand(128).tolist()
search_result = client.search(
collection_name="documents",
query_vector=search_vector,
limit=3
)
print(search_result)Qdrant - Golang示例
package main
import (
"context"
"fmt"
"log"
"math/rand"
"github.com/qdrant/go-client/qdrant"
"google.golang.org/grpc"
)
func main() {
// 连接到Qdrant
ctx := context.Background()
conn, err := grpc.Dial("localhost:6334", grpc.WithInsecure())
if err != nil {
log.Fatalf("无法连接到Qdrant: %v", err)
}
defer conn.Close()
client := qdrant.NewCollectionsClient(conn)
pointsClient := qdrant.NewPointsClient(conn)
// 创建集合
collectionName := "documents"
vectorSize := uint64(128)
// 删除已存在的集合
_, err = client.Delete(ctx, &qdrant.DeleteCollection{
CollectionName: collectionName,
})
// 忽略错误,如果集合不存在
// 创建新集合
_, err = client.Create(ctx, &qdrant.CreateCollection{
CollectionName: collectionName,
VectorsConfig: &qdrant.VectorsConfig{
Config: &qdrant.VectorsConfig_Params{
Params: &qdrant.VectorParams{
Size: vectorSize,
Distance: qdrant.Distance_Cosine,
},
},
},
})
if err != nil {
log.Fatalf("创建集合失败: %v", err)
}
// 上传向量
points := make([]*qdrant.PointStruct, 10)
for i := 0; i < 10; i++ {
// 创建随机向量
vector := make([]float32, vectorSize)
for j := 0; j < int(vectorSize); j++ {
vector[j] = rand.Float32()
}
// 创建点
points[i] = &qdrant.PointStruct{
Id: &qdrant.PointId{
PointIdOptions: &qdrant.PointId_Num{
Num: uint64(i),
},
},
Vectors: &qdrant.Vectors{
VectorsOptions: &qdrant.Vectors_Vector{
Vector: &qdrant.Vector{
Data: vector,
},
},
},
Payload: map[string]*qdrant.Value{
"text": {
Kind: &qdrant.Value_StringValue{
StringValue: fmt.Sprintf("文档 %d", i),
},
},
},
}
}
// 批量上传点
_, err = pointsClient.Upsert(ctx, &qdrant.UpsertPoints{
CollectionName: collectionName,
Points: points,
})
if err != nil {
log.Fatalf("上传点失败: %v", err)
}
// 搜索相似向量
searchVector := make([]float32, vectorSize)
for i := 0; i < int(vectorSize); i++ {
searchVector[i] = rand.Float32()
}
searchResult, err := pointsClient.Search(ctx, &qdrant.SearchPoints{
CollectionName: collectionName,
Vector: searchVector,
Limit: 3,
})
if err != nil {
log.Fatalf("搜索失败: %v", err)
}
fmt.Printf("搜索结果: %v\n", searchResult)
}选择建议
Milvus - 如果您需要高性能和可扩展性,特别适合大规模生产环境。它的两种语言客户端都很成熟。
Qdrant - 如果您注重易用性和灵活的过滤功能,Qdrant是很好的选择,同样有优秀的Go和Python支持。
Weaviate - 如果您的应用涉及更复杂的语义关系或需要多模态功能,Weaviate值得考虑。
性能对比
根据近期研究,三个数据库在加载和搜索性能上有显著差异。以下是基于一个语义搜索应用的测试结果(加载3680个向量,搜索前十个最相似的向量):
方面
Milvus
Pinecone
Weaviate
加载时间(每向量ms)
10 ms,需单独创建索引
4.3 ms,支持批量加载至1000向量
5 ms,支持批量处理
搜索时间(平均秒)
0.95 秒,L2距离默认,质量0.028
0.88 秒,余弦距离默认,质量0.03
0.12 秒,余弦距离默认,质量0.03
索引类型
支持HNSW、DiskANN等,需手动选择
隐含HNSW,无枚举选项
支持HNSW、Product Quantization
从表中可以看出,Weaviate在搜索时间上表现最佳,而Pinecone在加载时间上领先。Milvus的性能稍逊,但通过优化(如增加计算资源)可以提升。
功能对比
功能是选择数据库的重要考量因素,以下是三个数据库的功能差异:
功能
Milvus
Pinecone
Weaviate
混合搜索
不支持,限制较大
近期支持,闭源实现
支持,结合向量和关键词搜索
知识图谱支持
有限,主要聚焦向量搜索
不支持,专注于向量操作
支持,通过GraphQL和数据类
多租户支持
支持,通过分区和权限控制
支持,通过命名空间
支持,通过数据隔离
高级查询
支持元数据过滤,无GraphQL
基本查询,支持过滤
支持GraphQL,查询灵活
Weaviate因其GraphQL支持和混合搜索功能在复杂查询场景中表现突出。Milvus在多租户和元数据过滤方面有优势,但缺乏混合搜索。Pinecone近期增加了混合搜索,但作为闭源服务,灵活性受限。
可扩展性和部署
部署方式和可扩展性是另一个关键因素:
Milvus:开源,支持从本地到分布式系统的部署,适合自定义基础设施。Zilliz提供托管服务,定价为99美元/月(标准性能优化版)。
Pinecone:全托管服务,serverless架构,无需管理基础设施,定价为70美元/月(s1.x1 pod)。
Weaviate:开源,支持本地和云部署,定价为25美元/月(标准性能优化版),适合中小型项目。
Pinecone的托管服务适合快速部署,而Milvus和Weaviate更适合需要控制基础设施的场景。
语言支持
Milvus:提供官方Golang SDK(Milvus Go SDK Documentation)和Python SDK(Milvus Python SDK Documentation),文档齐全,社区活跃。
Pinecone:提供官方Golang SDK(Pinecone Go SDK Documentation)和Python SDK(Pinecone Python SDK Documentation),API简单,适合快速开发。
Weaviate:提供官方Golang客户端(Weaviate Go Client Documentation)和Python客户端(Weaviate Python Client Documentation),支持方法链式编程。
最后更新于