# 向量数据库对比与推荐

**引言**

向量数据库是专门为存储和查询高维向量数据设计的数据库，广泛应用于机器学习和人工智能领域，如语义搜索、推荐系统和检索增强生成（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示例

```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示例

```go
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示例

```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示例

```go
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)
}
```

### 选择建议

1. **Milvus** - 如果您需要高性能和可扩展性，特别适合大规模生产环境。它的两种语言客户端都很成熟。
2. **Qdrant** - 如果您注重易用性和灵活的过滤功能，Qdrant是很好的选择，同样有优秀的Go和Python支持。
3. **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](https://milvus.io/api-reference/go/v2.5.x/About.md)）和Python SDK（[Milvus Python SDK Documentation](https://milvus.io/docs/quickstart.md)），文档齐全，社区活跃。
* **Pinecone**：提供官方Golang SDK（[Pinecone Go SDK Documentation](https://docs.pinecone.io/reference/go-sdk)）和Python SDK（[Pinecone Python SDK Documentation](https://docs.pinecone.io/docs/quickstart)），API简单，适合快速开发。
* **Weaviate**：提供官方Golang客户端（[Weaviate Go Client Documentation](https://weaviate.io/developers/weaviate/client-libraries/go)）和Python客户端（[Weaviate Python Client Documentation](https://weaviate.io/developers/weaviate/client-libraries/python)），支持方法链式编程。
