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:必须与嵌入模型输出维度一致Distance:Cosine(余弦)、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 对比:
| 特性 | CreateSnapshotAsync | CreateFullSnapshotAsync |
|---|---|---|
| 范围 | 单个集合 | 所有集合 |
| 大小 | 较小 | 较大 |
| 速度 | 快 | 慢 |
| 使用场景 | 单集合备份 | 整库备份/迁移 |
🔗 方法关联图
┌─────────────────────────────────────────────────────────────────┐ │ 项目初始化流程 │ └─────────────────────────────────────────────────────────────────┘ 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 |
无 | CreateAliasAsync, CreatePayloadIndexAsync, CreateVectorNameAsync |
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 是否 >= 80 → 100 万次检查 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 向量

浙公网安备 33010602011771号