1.18.1版本Qdrant 的8个核心创建方法

十年河东,十年河西,莫欺少年穷,学无止境,精益求精

📦 Qdrant 核心创建方法详解

这 8 个方法都是资源创建类操作,按功能可分为 5 大类:

🗂️ 方法分类总览

类别方法作用使用频率
集合管理 CreateCollectionAsync 创建集合 ⭐⭐⭐⭐⭐
集合管理 RecreateCollectionAsync 重建集合(先删后创) ⭐⭐
集合管理 CreateAliasAsync 创建集合别名 ⭐⭐⭐
索引管理 CreatePayloadIndexAsync 创建 Payload 字段索引 ⭐⭐⭐⭐
向量管理 CreateVectorNameAsync 创建命名向量 ⭐⭐
分片管理 CreateShardKeyAsync 创建分片键(分布式)
快照管理 CreateSnapshotAsync 创建集合快照 ⭐⭐⭐
快照管理 CreateFullSnapshotAsync 创建全量快照 ⭐⭐

1️⃣ CreateCollectionAsync(核心)

📊 概念对照表

Qdrant关系型数据库说明
Point Row(行) 一条数据记录
Collection Table(表) 数据集合
Vector 列(特殊类型) 向量数据(浮点数组)
Payload 其他列 元数据(文本、数字等)
Point ID Primary Key 主键(唯一标识)
Index Index 索引(加速查询)

作用:创建向量集合(类似 SQL 的 CREATE TABLE)

await client.CreateCollectionAsync(
    collectionName: "documents",
    vectorsConfig: new VectorParams
    {
        Size = 1536,           // 向量维度
        Distance = Distance.Cosine  // 距离度量
    },
    shardNumber: 1,            // 分片数(分布式)
    replicationFactor: 1,      // 副本数(分布式)
    onDiskPayload: false,      // 是否将 payload 存磁盘
    hnswConfig: new HnswConfigDiff { M = 16, EfConstruct = 100 },  // HNSW 索引参数
    optimizersConfig: new OptimizersConfigDiff { MaxSegmentSize = 100000 },  // 优化器参数
    walConfig: new WalConfigDiff { WalCapacityMb = 32 },  // WAL 配置
    quantizationConfig: new QuantizationConfig { Scalar = new() { Type = ScalarType.Int8 } },  // 量化配置
    sparseVectorsConfig: null,  // 稀疏向量配置
    strictModeConfig: null      // 严格模式配置
);

关键参数

  • Size:必须与嵌入模型输出维度一致
  • DistanceCosine(余弦)、Euclid(欧氏)、Dot(点积)
  • onDiskPayload:大数据集开启可节省内存

使用场景:项目初始化时创建存储结构

2️⃣ RecreateCollectionAsync(重建)

作用:先删除已存在的集合,再创建新集合(原子操作)

await client.RecreateCollectionAsync(
    collectionName: "documents",
    vectorsConfig: new VectorParams { Size = 1536, Distance = Distance.Cosine }
);

内部逻辑

1. 检查集合是否存在
2. 如果存在 → DeleteCollectionAsync
3. 执行 CreateCollectionAsync

使用场景

  • ✅ 开发/测试环境重置数据
  • ✅ 向量维度变更需要重建
  • ❌ 生产环境慎用(会丢失数据)

3️⃣ CreateAliasAsync(别名)

作用:为集合创建逻辑别名,实现零停机切换

// 创建别名
await client.CreateAliasAsync(
    aliasName: "prod-documents",
    collectionName: "documents-v2"
);

// 切换别名(原子操作)
await client.UpdateAliasesAsync(new[]
{
    new AliasOperations
    {
        RenameAlias = new RenameAlias
        {
            OldAliasName = "prod-documents",
            NewAliasName = "documents-v1"  // 旧版本降级
        }
    },
    new AliasOperations
    {
        CreateAlias = new CreateAlias
        {
            AliasName = "prod-documents",  // 新版本上线
            CollectionName = "documents-v3"
        }
    }
});

使用场景

  • 🔄 蓝绿部署(零停机切换)
  • 🔄 A/B 测试(不同模型版本)
  • 🔄 数据迁移(无缝切换)

与 CreateCollectionAsync 的关系

CreateCollectionAsync("documents-v1") → CreateAliasAsync("prod", "documents-v1")
CreateCollectionAsync("documents-v2") → UpdateAliasesAsync("prod""documents-v2")

4️⃣ CreatePayloadIndexAsync(索引)

作用:为 Payload 字段创建索引,加速过滤查询

// 关键词索引(精确匹配)
await client.CreatePayloadIndexAsync(
    collectionName: "documents",
    fieldName: "category",
    schemaType: PayloadSchemaType.Keyword,
    indexParams: new KeywordIndexParams { IsTenable = true }
);

// 数值索引(范围查询)
await client.CreatePayloadIndexAsync(
    collectionName: "documents",
    fieldName: "score",
    schemaType: PayloadSchemaType.Float,
    indexParams: new FloatIndexParams { Lookup = true, Range = true }
);

// 全文索引(文本搜索)
await client.CreatePayloadIndexAsync(
    collectionName: "documents",
    fieldName: "content",
    schemaType: PayloadSchemaType.Text,
    indexParams: new TextIndexParams { Tokenizer = TokenizerType.Word }
);

// 时间索引(时间范围查询)
await client.CreatePayloadIndexAsync(
    collectionName: "documents",
    fieldName: "created_at",
    schemaType: PayloadSchemaType.Datetime,
    indexParams: new DatetimeIndexParams { Range = true }
);

索引类型

PayloadSchemaType适用场景支持操作
Keyword 分类、标签 精确匹配、IN 查询
Integer/Float 分数、价格 范围查询(>, <, >=, <=)
Bool 状态标记 布尔匹配
Text 全文搜索 分词匹配
Datetime 时间戳 时间范围
Geo 地理位置 地理围栏
Uuid 唯一标识 精确匹配

与 CreateCollectionAsync 的关系

CreateCollectionAsync → 插入数据 → CreatePayloadIndexAsync → 带过滤的 SearchAsync

⚠️ 注意:索引应在插入数据前或数据量少时创建,大量数据后创建索引会很慢。

5️⃣ CreateVectorNameAsync(命名向量)

作用:为集合添加多向量支持(一个点存储多个向量)

// 创建集合时定义默认向量
await client.CreateCollectionAsync("multi-vector", new VectorParams
{
    Size = 768,
    Distance = Distance.Cosine
});

// 添加额外命名向量
await client.CreateVectorNameAsync(new CreateVectorNameRequest
{
    CollectionName = "multi-vector",
    VectorName = "title-vector",  // 标题向量
    VectorParams = new VectorParams
    {
        Size = 384,
        Distance = Distance.Cosine
    }
});

await client.CreateVectorNameAsync(new CreateVectorNameRequest
{
    CollectionName = "multi-vector",
    VectorName = "content-vector",  // 内容向量
    VectorParams = new VectorParams
    {
        Size = 1536,
        Distance = Distance.Cosine
    }
});

使用场景

  • 📝 标题 + 正文分别向量化
  • 🖼️ 图片 + 文本多模态
  • 🔍 多粒度检索(粗向量 + 细向量)

插入命名向量

await client.UpsertAsync("multi-vector", new[]
{
    new PointStruct
    {
        Id = "doc1",
        Vectors = new NamedVectors
        {
            Vectors =
            {
                ["title-vector"] = titleEmbedding,
                ["content-vector"] = contentEmbedding
            }
        }
    }
});

搜索指定向量

var results = await client.SearchAsync(
    collectionName: "multi-vector",
    vector: queryVector,
    vectorName: "title-vector",  // 指定向量
    limit: 10
);

6️⃣ CreateShardKeyAsync(分片键)

作用:为分布式集群创建自定义分片策略

// 创建字符串分片键
await client.CreateShardKeyAsync(
    collectionName: "documents",
    createShardKey: new CreateShardKey
    {
        ShardKey = new ShardKey { Keyword = "tenant-a" },  // 租户 A
        ShardsNumber = 2,  // 该租户占用 2 个分片
        ReplicationFactor = 2  // 2 个副本
    }
);

// 创建数字分片键
await client.CreateShardKeyAsync(
    collectionName: "documents",
    createShardKey: new CreateShardKey
    {
        ShardKey = new ShardKey { Number = 1 },  // 分片 ID
        ShardsNumber = 1,
        ReplicationFactor = 1
    }
);

使用场景

  • 🏢 多租户隔离(每个租户独立分片)
  • 🌍 地域分片(不同区域数据物理隔离)
  • 📊 冷热数据分离

⚠️ 注意:仅适用于分布式集群模式,单机版无需使用。

7️⃣ CreateSnapshotAsync(集合快照)

作用:为单个集合创建备份快照

var snapshot = await client.CreateSnapshotAsync("documents");
Console.WriteLine($"快照创建成功:{snapshot.Name}");
Console.WriteLine($"大小:{snapshot.Size} bytes");
Console.WriteLine($"路径:{snapshot.Path}");

使用场景

  • 💾 定期备份(每日/每周)
  • 🔄 数据迁移前备份
  • 🧪 创建测试数据集

恢复快照(通过 Qdrant API 或命令行):

curl -X PUT "http://localhost:6333/collections/documents/snapshots/recover" \
  -H "Content-Type: application/json" \
  -d '{"location": "/path/to/snapshot.snapshot"}'

8️⃣ CreateFullSnapshotAsync(全量快照)

作用:为整个 Qdrant 实例创建快照(所有集合)

var fullSnapshot = await client.CreateFullSnapshotAsync();
Console.WriteLine($"全量快照:{fullSnapshot.Name}");

与 CreateSnapshotAsync 对比

特性CreateSnapshotAsyncCreateFullSnapshotAsync
范围 单个集合 所有集合
大小 较小 较大
速度
使用场景 单集合备份 整库备份/迁移

🔗 方法关联图

┌─────────────────────────────────────────────────────────────────┐
│                     项目初始化流程                               │
└─────────────────────────────────────────────────────────────────┘

  1. CreateCollectionAsync          ← 创建集合(必须第一步)
         │
         ├─→ 2. CreateVectorNameAsync      ← 添加多向量支持(可选)
         │
         ├─→ 3. CreatePayloadIndexAsync    ← 创建字段索引(推荐)
         │
         ├─→ 4. CreateAliasAsync           ← 创建别名(生产环境推荐)
         │
         └─→ 5. CreateShardKeyAsync        ← 创建分片键(分布式可选)
         
  6. 插入数据 (UpsertAsync)
  
  7. 定期维护:
         ├─→ CreateSnapshotAsync     ← 集合备份
         └─→ CreateFullSnapshotAsync ← 全库备份

  8. 版本升级:
         RecreateCollectionAsync 或 CreateAliasAsync 切换

📋 最佳实践顺序

开发环境

// 1. 重建集合(清空旧数据)
await client.RecreateCollectionAsync("dev-docs", vectorParams);

// 2. 创建索引
await client.CreatePayloadIndexAsync("dev-docs", "category", PayloadSchemaType.Keyword);

// 3. 插入测试数据
await client.UpsertAsync(...);

生产环境

// 1. 创建新集合(新版本)
await client.CreateCollectionAsync("docs-v2", vectorParams);

// 2. 创建索引
await client.CreatePayloadIndexAsync("docs-v2", "category", PayloadSchemaType.Keyword);
await client.CreatePayloadIndexAsync("docs-v2", "score", PayloadSchemaType.Float);

// 3. 创建别名(首次)
await client.CreateAliasAsync("prod-docs", "docs-v2");

// 4. 迁移数据(后台)
await MigrateDataAsync("docs-v1", "docs-v2");

// 5. 切换别名(零停机)
await client.UpdateAliasesAsync(new[]
{
    new AliasOperations { RenameAlias = new() { OldAliasName = "prod-docs", NewAliasName = "docs-v1-backup" } },
    new AliasOperations { CreateAlias = new() { AliasName = "prod-docs", CollectionName = "docs-v2" } }
});

// 6. 创建备份
await client.CreateSnapshotAsync("docs-v1-backup");

⚠️ 依赖关系总结

方法依赖被依赖
CreateCollectionAsync CreateAliasAsyncCreatePayloadIndexAsyncCreateVectorNameAsync
RecreateCollectionAsync 无(会删除已有数据)
CreateAliasAsync 集合已存在
CreatePayloadIndexAsync 集合已存在
CreateVectorNameAsync 集合已存在
CreateShardKeyAsync 集合已存在 + 分布式模式
CreateSnapshotAsync 集合已存在
CreateFullSnapshotAsync

🎯 一句话总结

CreateCollectionAsync 是地基,其他方法都是在地基上盖房子。
RecreateCollectionAsync 是拆了重建,CreateAliasAsync 是门牌号,
CreatePayloadIndexAsync 是目录索引,CreateVectorNameAsync 是多房间,
CreateShardKeyAsync 是分区隔断,CreateSnapshotAsync 是备份照片。

随之而来的问题:

CreateAliasAsync 、CreatePayloadIndexAsync、 CreateVectorNameAsync 这三个方法都是在CreateCollectionAsync之后使用

1、为什么要为集合创建别名?

2、创建 Payload 字段索引的作用?

3、为什么需要创建命名向量 ?

🎯 三个核心问题深度解析

这三个方法确实都在 CreateCollectionAsync 之后使用,但它们解决的是完全不同维度的问题。来,逐个拆解:


1️⃣ CreateAliasAsync —— 为什么要为集合创建别名?

📌 核心答案:解耦 + 零停机切换

场景对比

没有别名时

应用代码 → 硬编码集合名 "documents-v1"

需要升级时:
1. 创建新集合 "documents-v2"
2. 迁移数据(耗时)
3. 停服
4. 修改代码,把 "documents-v1" 改成 "documents-v2"
5. 重新部署
6. 开服

问题:停机、风险大、回滚困难

有别名后

应用代码 → 别名 "prod-documents" → 实际集合 "documents-v1"

需要升级时:
1. 创建新集合 "documents-v2"(不影响线上)
2. 后台迁移数据(用户无感知)
3. 原子切换别名: "prod-documents""documents-v2"
4. 完成(零停机)

回滚?再切一次别名即可,1 秒搞定

🏪 生活类比

概念类比
集合 具体的店铺(如"XX 路 1 号商铺")
别名 招牌名称(如"张哥便利店")
切换别名 换个地方开店,招牌不变,顾客无感知

 
顾客说:"我去张哥便利店买东西"
              ↓
实际去的是:XX 路 1 号商铺(v1 版本)

后来店铺搬到 YY 路 2 号(v2 版本)
招牌还是"张哥便利店"
顾客不需要知道地址变了

💼 真实生产场景

// 应用代码永远只认别名
var collectionName = "prod-documents";  // 别名

// 第一次部署
await client.CreateCollectionAsync("documents-v1", ...);
await client.CreateAliasAsync("prod-documents", "documents-v1");

// 三个月后需要升级(向量维度从 768 改成 1536)
await client.CreateCollectionAsync("documents-v2", ...);  // 新集合
await MigrateDataAsync("documents-v1", "documents-v2");    // 后台迁移

// 原子切换(毫秒级,用户无感知)
await client.UpdateAliasesAsync(new[]
{
    new AliasOperations { RenameAlias = new() { 
        OldAliasName = "prod-documents", 
        NewAliasName = "documents-v1-backup" 
    }},
    new AliasOperations { CreateAlias = new() { 
        AliasName = "prod-documents", 
        CollectionName = "documents-v2" 
    }}
});

// 出问题?秒回滚
await client.UpdateAliasesAsync(new[]
{
    new AliasOperations { RenameAlias = new() { 
        OldAliasName = "documents-v1-backup", 
        NewAliasName = "prod-documents" 
    }}
});

✅ 什么时候必须用别名?

场景是否需要原因
个人项目/ Demo ❌ 不需要 没有停机成本
24/7 在线服务 ✅ 必须 不能停机维护
频繁迭代模型 ✅ 强烈建议 随时回滚
A/B 测试 ✅ 必须 快速切换实验组

2️⃣ CreatePayloadIndexAsync —— 创建 Payload 字段索引的作用?

📌 核心答案:让过滤查询从"全表扫描"变成"索引查找"

没有索引时会发生什么?

// 假设集合有 100 万条数据
var filter = new Filter
{
    Must = new List<Condition>
    {
        Condition.CreateMatch("category", "tech"),  // 找科技类
        Condition.CreateRange("score", new Range { Gte = 80f })  // 分数>80
    }
};

var results = await client.SearchAsync(
    collectionName: "documents",
    vector: queryVector,
    filter: filter,  // ⚠️ 没有索引
    limit: 10
);
执行过程: 
1. 计算查询向量与所有 100 万条数据的相似度 → 100 万次计算
2. 逐条检查每条数据的 category 是否是 "tech"100 万次检查
3. 逐条检查 score 是否 >= 80100 万次检查
4. 排序,返回前 10 条

耗时:可能 5-10

有索引后会发生什么?

// 先创建索引
await client.CreatePayloadIndexAsync(
    collectionName: "documents",
    fieldName: "category",
    schemaType: PayloadSchemaType.Keyword  // 关键词索引
);

await client.CreatePayloadIndexAsync(
    collectionName: "documents",
    fieldName: "score",
    schemaType: PayloadSchemaType.Float,
    indexParams: new FloatIndexParams { Range = true }  // 范围索引
);

// 同样的查询
var results = await client.SearchAsync(
    collectionName: "documents",
    vector: queryVector,
    filter: filter,  // ✅ 有索引
    limit: 10
);
执行过程:

Copy
1. 通过 category 索引,直接定位到所有 "tech" 类别的数据 → 假设 10 万条
2. 通过 score 索引,在这 10 万条中快速找到 score >= 80 的 → 假设 1 万条
3. 只计算这 1 万条与查询向量的相似度 → 1 万次计算
4. 排序,返回前 10 条

耗时:可能 0.1-0.5

性能提升:10-50 倍

📊 索引类型与适用场景

// 1. Keyword 索引 - 精确匹配
await client.CreatePayloadIndexAsync("docs", "category", PayloadSchemaType.Keyword);
// 适用:category == "tech", category IN ["tech", "news"]

// 2. Float/Integer 索引 - 范围查询
await client.CreatePayloadIndexAsync("docs", "score", PayloadSchemaType.Float, 
    new FloatIndexParams { Range = true });
// 适用:score > 80, score BETWEEN 60 AND 90

// 3. Bool 索引 - 状态过滤
await client.CreatePayloadIndexAsync("docs", "is_published", PayloadSchemaType.Bool);
// 适用:is_published == true

// 4. Datetime 索引 - 时间范围
await client.CreatePayloadIndexAsync("docs", "created_at", PayloadSchemaType.Datetime,
    new DatetimeIndexParams { Range = true });
// 适用:created_at > "2026-01-01"

// 5. Text 索引 - 全文搜索
await client.CreatePayloadIndexAsync("docs", "content", PayloadSchemaType.Text,
    new TextIndexParams { Tokenizer = TokenizerType.Word });
// 适用:content CONTAINS "人工智能"

🏪 生活类比

场景没有索引有索引
图书馆找书 一本本翻(全表扫描) 查目录卡片(索引查找)
Excel 筛选 逐行检查 建立筛选器后秒出
手机联系人 从头翻到尾 按拼音首字母快速定位

⚠️ 索引的代价

优点:查询快 10-50 倍
代价:
1. 占用额外存储空间(约 10-30%2. 插入/更新时稍慢(需要维护索引)
3. 创建索引本身耗时(大数据集可能几分钟)

建议

  • ✅ 经常用于过滤的字段 → 建索引
  • ❌ 几乎不用于过滤的字段 → 不建索引
  • ❌ 高基数字段(如 UUID)→ 建了也没用

3️⃣ CreateVectorNameAsync —— 为什么需要创建命名向量?

📌 核心答案:一个数据存储多个向量,支持多粒度/多模态检索

单向量 vs 多向量

单向量(默认)

一条数据 = 一个向量

文档 "人工智能简介"
  └─> [0.1, 0.5, 0.8, ...]  (1536 维,全文向量)

搜索时:只能用全文向量匹配
问题:标题很相关但正文不相关的内容搜不出来

多维向量(命名向量)

一条数据 = 多个向量

文档 "人工智能简介"
  ├─> title-vector:    [0.9, 0.1, 0.3, ...]  (384 维,标题向量)
  ├─> content-vector:  [0.1, 0.5, 0.8, ...]  (1536 维,正文向量)
  └─> keyword-vector:  [0.7, 0.2, 0.6, ...]  (768 维,关键词向量)

搜索时:可以指定用哪个向量匹配
优势:灵活、精准、多场景

🎯 真实使用场景

场景 1:标题 + 正文分别检索

// 创建集合(默认向量)
await client.CreateCollectionAsync("articles", new VectorParams
{
    Size = 1536,  // 正文向量维度
    Distance = Distance.Cosine
});

// 添加标题向量
await client.CreateVectorNameAsync(new CreateVectorNameRequest
{
    CollectionName = "articles",
    VectorName = "title",  // 命名向量
    VectorParams = new VectorParams
    {
        Size = 384,  // 标题向量维度(可以用小模型)
        Distance = Distance.Cosine
    }
});

// 插入数据(多向量)
await client.UpsertAsync("articles", new[]
{
    new PointStruct
    {
        Id = "article-1",
        Vectors = new NamedVectors
        {
            Vectors =
            {
                ["title"] = titleEmbedding,      // 标题向量
                ["default"] = contentEmbedding   // 正文向量(默认)
            }
        },
        Payload =
        {
            ["title"] = "人工智能入门指南",
            ["content"] = "正文内容..."
        }
    }
});

// 搜索 - 用户搜标题关键词
var titleResults = await client.SearchAsync(
    collectionName: "articles",
    vector: queryTitleEmbedding,
    vectorName: "title",  // ⭐ 指定用标题向量搜索
    limit: 10
);

// 搜索 - 用户搜正文内容
var contentResults = await client.SearchAsync(
    collectionName: "articles",
    vector: queryContentEmbedding,
    vectorName: "default",  // 用正文向量
    limit: 10
);

效果对比

搜索类型单向量多向量
搜标题 准确率 60% 准确率 90%
搜正文 准确率 85% 准确率 85%
混合搜 无法实现 加权融合

场景 2:多模态检索(图 + 文)

// 创建多模态集合
await client.CreateCollectionAsync("multimodal", new VectorParams
{
    Size = 512,  // 图像向量维度
    Distance = Distance.Cosine
});

// 添加文本向量
await client.CreateVectorNameAsync(new CreateVectorNameRequest
{
    CollectionName = "multimodal",
    VectorName = "text",
    VectorParams = new VectorParams { Size = 768, Distance = Distance.Cosine }
});

// 插入数据(图 + 文)
await client.UpsertAsync("multimodal", new[]
{
    new PointStruct
    {
        Id = "product-1",
        Vectors = new NamedVectors
        {
            Vectors =
            {
                ["image"] = imageEmbedding,   // CLIP 图像向量
                ["text"] = textEmbedding      // CLIP 文本向量
            }
        },
        Payload =
        {
            ["name"] = "运动鞋",
            ["description"] = "舒适透气的跑步鞋"
        }
    }
});

// 以图搜图
var imageResults = await client.SearchAsync(
    collectionName: "multimodal",
    vector: queryImageEmbedding,
    vectorName: "image",
    limit: 10
);

// 以文搜图(用文字描述找图片)
var textResults = await client.SearchAsync(
    collectionName: "multimodal",
    vector: queryTextEmbedding,
    vectorName: "text",
    limit: 10
);

🏪 生活类比

场景单向量多向量
找人 只看全身照 有正面照 + 侧面照 + 指纹
买房 只看总价 可分别搜房价/面积/地段
买手机 只能搜"手机" 可搜外观/配置/价格

✅ 什么时候需要命名向量?

需求是否需要原因
简单文档搜索 ❌ 不需要 单向量够用
标题 + 正文分别搜索 ✅ 需要 不同粒度
图 + 文多模态 ✅ 需要 不同模态
多语言混合 ✅ 需要 不同嵌入模型
粗检索 + 精检索 ✅ 需要 不同阶段

📊 三者关系总结

CreateCollectionAsync
        │
        ├──→ CreateAliasAsync        ← 解决"如何无感切换"(运维层面)
        │
        ├──→ CreatePayloadIndexAsync ← 解决"如何快速过滤"(查询性能)
        │
        └──→ CreateVectorNameAsync   ← 解决"如何多维度检索"(数据模型)
方法层面解决的问题不用会怎样
CreateAliasAsync 运维 零停机部署 升级要停机
CreatePayloadIndexAsync 性能 过滤查询加速 查询慢 10-50 倍
CreateVectorNameAsync 模型 多粒度检索 只能单维度搜索

🎯 一句话总结

别名 = 店铺招牌(换地址不用告诉顾客)
索引 = 图书目录(找书不用一本本翻)
命名向量 = 多张照片(看人不止看正面)

CreateCollectionAsync 只是创建了一个空集合,里面的数据怎么插入 / 更新 /删除??

📦 Qdrant 数据操作详解(增删改查)

你说得对!CreateCollectionAsync 只是创建了空壳子(类似 SQL 的 CREATE TABLE),数据操作要用另一套方法。


🗂️ 数据操作总览

操作方法说明
插入/更新 UpsertAsync 新增或更新点(不存在则插入,存在则覆盖)
删除 DeleteAsync 删除点(按 ID 或过滤条件)
查询 SearchAsync / RetrieveAsync / ScrollAsync 向量搜索 / 按 ID 查询 / 滚动扫描
更新 Payload SetPayloadAsync / OverwritePayloadAsync 部分更新 / 完全覆盖
更新向量 UpdateVectorsAsync 只更新向量,不改 Payload
删除 Payload DeletePayloadAsync / ClearPayloadAsync 删除字段 / 清空所有

1️⃣ 插入/更新数据(UpsertAsync)

单条插入

sing Qdrant.Client;
using Qdrant.Client.Grpc;

var client = new QdrantClient("localhost");

// 方法 1:用 Guid 作为 ID
var pointId = Guid.NewGuid();
var point = new PointStruct
{
    Id = pointId,
    Vector = new float[] { 0.1f, 0.5f, 0.8f, ... },  // 1536 维向量
    Payload =
    {
        ["text"] = "文档内容",
        ["category"] = "tech",
        ["score"] = 85.5f,
        ["created_at"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
        ["tags"] = "AI,机器学习,深度学习"
    }
};

var result = await client.UpsertAsync("documents", new[] { point });
Console.WriteLine($"插入成功,状态:{result.Status}");

批量插入(推荐)

var points = new List<PointStruct>();

for (int i = 0; i < 100; i++)
{
    points.Add(new PointStruct
    {
        Id = (ulong)(i + 1),  // 用数字 ID
        Vector = GenerateVector(1536),
        Payload =
        {
            ["text"] = $"文档内容 {i}",
            ["category"] = i % 3 == 0 ? "tech" : "news",
            ["score"] = (float)(new Random().NextDouble() * 100)
        }
    });
}

// 批量插入(一次最多 100-1000 条)
var result = await client.UpsertAsync("documents", points);
Console.WriteLine($"批量插入 {points.Count} 条,状态:{result.Status}");

用字符串 ID(业务 ID)

var point = new PointStruct
{
    Id = "doc_20260731_001",  // 业务 ID(字符串)
    Vector = vector,
    Payload =
    {
        ["text"] = "内容",
        ["biz_id"] = "ORDER_12345"  // 关联业务 ID
    }
};

await client.UpsertAsync("documents", new[] { point });

更新数据(同样的方法!)

// Upsert = Insert + Update
// 如果 ID 已存在 → 覆盖更新
// 如果 ID 不存在 → 新增

var existingId = "doc_001";

// 第一次调用 → 插入
await client.UpsertAsync("documents", new[]
{
    new PointStruct
    {
        Id = existingId,
        Vector = vector1,
        Payload = { ["text"] = "初版内容", ["version"] = 1 }
    }
});

// 第二次调用 → 更新(覆盖)
await client.UpsertAsync("documents", new[]
{
    new PointStruct
    {
        Id = existingId,  // 相同 ID
        Vector = vector2,  // 新向量
        Payload = { ["text"] = "更新后内容", ["version"] = 2 }  // 新 Payload
    }
});

2️⃣ 删除数据(DeleteAsync)

按单个 ID 删除

// 删除 Guid ID
await client.DeleteAsync("documents", guidId);

// 删除数字 ID
await client.DeleteAsync("documents", 12345ul);

// 删除字符串 ID
await client.DeleteAsync("documents", "doc_001");

批量删除(按 ID 列表)

// 删除多个 Guid
await client.DeleteAsync("documents", new[] { id1, id2, id3 });

// 删除多个数字 ID
await client.DeleteAsync("documents", new[] { 1ul, 2ul, 3ul });

// 删除多个字符串 ID
await client.DeleteAsync("documents", new[] { "doc_001", "doc_002", "doc_003" });

按过滤条件删除(批量删除神器)

// 删除所有 category = "obsolete" 的数据
var filter = new Filter
{
    Must = new List<Condition>
    {
        Condition.CreateMatch("category", "obsolete")
    }
};

await client.DeleteAsync("documents", filter);

// 删除 score < 60 的数据
var filter2 = new Filter
{
    Must = new List<Condition>
    {
        Condition.CreateRange("score", new Range { Lt = 60f })
    }
};

await client.DeleteAsync("documents", filter2);

// 组合条件:删除 tech 类别且分数<60 的
var filter3 = new Filter
{
    Must = new List<Condition>
    {
        Condition.CreateMatch("category", "tech"),
        Condition.CreateRange("score", new Range { Lt = 60f })
    }
};

await client.DeleteAsync("documents", filter3);

删除整个集合

await client.DeleteCollectionAsync("documents");

3️⃣ 查询数据

向量相似度搜索(核心功能)

var queryVector = GenerateVector(1536);

var results = await client.SearchAsync(
    collectionName: "documents",
    vector: queryVector,
    limit: 10,  // 返回数量
    offset: 0,
    filter: null,  // 过滤条件(可选)
    withPayload: true,  // 返回 Payload
    withVector: false   // 不返回向量(节省流量)
);

foreach (var result in results)
{
    Console.WriteLine($"ID: {result.Id}");
    Console.WriteLine($"相似度:{result.Score}");  // 0-1,越接近 1 越相似
    Console.WriteLine($"内容:{result.Payload.Fields["text"].AsString()}");

 

⚠️ 关键注意事项

问题说明解决方案
Upsert 是覆盖更新 不会合并 Payload,会完全替换 部分更新用 SetPayloadAsync
批量操作限制 单次最多 100-1000 条 大数据集分批次处理
向量维度必须一致 插入的向量维度必须与集合定义一致 确保嵌入模型输出正确
ID 类型混用 不要混用 Guid/ulong/字符串 ID 统一用一种类型
删除后不可恢复 没有回收站机制 操作前先备份

 

🎯 一句话总结

CreateCollectionAsync = 建表
UpsertAsync = INSERT/UPDATE(主键存在则更新,不存在则插入)
DeleteAsync = DELETE
SearchAsync = SELECT + 向量相似度排序
SetPayloadAsync = UPDATE 部分字段
UpdateVectorsAsync = UPDATE 向量

 


 
posted @ 2026-07-31 15:01  天才卧龙  阅读(32)  评论(0)    收藏  举报