3.2 数据操作:CRUD


3.2 数据操作:CRUD

本节摘要:CRUD 是向量数据生命周期的主干——建集合、插点、读点、改点、删点。Qdrant 里最值得记的一个词是 upsert:它把"插入"和"更新"合成一个操作,"有则更新、无则插入",天生幂等。这一节先讲清集合、点、向量、有效载荷这层数据模型,再拆读操作里"按 ID 精确取"和"按相似度搜索"两条不同的路,最后用一张生命周期图把五件事串起来。读完你能独立完成一个集合从建到删的全流程,并知道批量操作为什么比逐条操作更该用。

学习目标

阅读完本节,你应当能够:

  1. 说明集合、点、向量、有效载荷各自扮演的角色和彼此关系。
  2. 解释 upsert 的幂等含义,以及它为什么能省掉大量"先查再决定插还是改"的判断。
  3. 区分精确取点、相似搜索、分页遍历三种读操作的使用场景。
  4. 完成按 ID 删除与按条件删除,并理解软删除与物理清理的区别。
  5. 说出批量操作相比逐条操作在性能上的收益与代价。

一、问题与直觉

数据不会自己住进数据库。你手里可能是一批商品描述、一叠文档切片、一组用户画像,它们得先变成向量,再连同原始信息一起放进 Qdrant,之后才谈得上"搜最像的"。这条"放进去—读出来—改一改—删掉"的通道,就是 CRUD。

很多人第一次碰向量库,会习惯性地用关系型数据库的思维去套:表、行、列。方向没错,但对象换了。关系库里你操作的是"表里的一行";在 Qdrant 里,你操作的是"集合里的一个点",这个点身上背着一条向量和一堆元数据。向量负责"像不像",元数据负责"是什么、什么条件"。两者绑在一起,才能既搜得准、又筛得精。

所以本节要解决的第一个问题不是"API 长什么样",而是"我到底在操作什么对象"。对象没搞清,后面写过滤条件、设计数据模型都会跑偏。

二、核心原理

2.1 数据模型:集合、点、向量、有效载荷

最外层是集合,它是向量的逻辑分组。建集合时要定两件硬事:向量维度,以及距离度量。维度决定向量空间的形状,距离度量决定"像不像"怎么算。这两件事一旦定下,同一集合里所有向量都得遵守。

往里一层是点,它是操作的基本单位。一个点至少有三样东西:一个唯一的点 ID,用来精确定位;一条或多条向量,用来参与相似度计算;一堆可选的元数据,也就是有效载荷,用来做过滤和结果展示。有效载荷可以是任意能序列化成 JSON 的结构化数据——原文、标签、时间戳、分类都行。

这套模型可以用一句话概括:集合管维度,点管身份,向量管相似,载荷管过滤。

2.2 upsert:一个词省掉一半判断

传统思路里,插入和更新是两件事:先查这个 ID 在不在,在就更新,不在就插入。这个"先查再决定"的步骤,在网络环境里既费时又容易出竞态——两个请求同时查都以为"不存在",结果各自插,最后一个覆盖另一个。

Qdrant 用 upsert 把这件事合并了。它的语义是:这个 ID 存在,就更新它的向量和载荷;不存在,就新建一个点。你不需要先查,直接提交即可。这个特性让重试变得安全——同一条数据重复提交,结果一样,不会插出重复点。批量导入时尤其省心:一个列表丢进去,是全新数据还是增量更新,都由 ID 说了算。

2.3 读操作的三条路

读操作最容易混淆,因为它其实有三条不同的路,用途完全不同。

按 ID 精确取点,就是"我知道编号,把这条完整取出来"。它不做相似度计算,是直接的定位操作,适合拿到搜索结果 ID 后再去取原文、取完整字段。

相似度搜索,才是向量库的主场。你给一条查询向量,它去向量空间里找距离最近的 K 个点。这是近似最近邻搜索,快,但追求的是"够近"而非"绝对最近"。

分页遍历则既不算相似度、也不按单个 ID 取,而是按顺序或按过滤条件一页页扫过集合里的点,适合"把所有符合某条件的数据导出来"这类批处理任务。

读操作 定位方式 是否算相似度 典型用途
精确取点 按点 ID 取搜索结果对应的原文与完整字段
相似搜索 按查询向量 语义检索、推荐、以图搜图
分页遍历 按顺序或过滤条件 批量导出、按条件全量扫描

2.4 一致性:先写日志再落库

数据写进去,怎么保证不丢?Qdrant 用的是写前日志的思路:任何修改先记到日志里,再应用到实际的存储和索引。这样即使写入中途系统崩了,重启后也能靠重放日志把数据恢复到一致状态。批量写入时,日志的写盘会被优化合并,减少磁盘 I/O。理解这一层,你就明白为什么"等操作返回完成"这个选项在生产里值得打开——它意味着数据已经安全落到了日志,而不只是进了内存。

三、工程实践要点

写操作里,批量是关键词。逐条插入意味着每条都要一次网络往返,十万条就是十万次往返,其中大部分时间花在"发请求—等响应"上。批量把数据打包成一次请求,网络往返次数从十万降到几百甚至更少,服务端也能更高效地批处理。代价是单次请求体积变大、失败时影响面更宽,所以要在"包多大"和"失败重传成本"之间找一个平衡点。

更新操作里有一个容易被忽视的能力:部分更新。你不必每次把整个点的向量和载荷都重写一遍,可以只改载荷里的某几个字段。数据清洗、打标签、模型迭代后重新嵌入向量,这些场景都靠它。用对了,省的不只是带宽,还有"整点重写"可能带来的数据不一致窗口。

删除则要分清逻辑删除和物理删除。逻辑删除是先把点标记成"已删除",响应快,数据还在磁盘上;物理删除是在后台的整理或垃圾回收阶段,把数据和索引里的节点真正清掉、做碎片整理。理解这一点,你才不会对"刚删完存储空间没立刻降下来"感到意外。

写入时还有一个常被忽略的开关:是否等待操作真正落盘。默认的"提交就返回"很快,但数据可能还在内存里;等待落盘再返回,慢一点,却给了你"写成功了"的确定性。批量导入可以适当放宽等待,关键的单条写入建议等待完成,避免程序继续往下走时数据其实还没安全落库。

⚠️ 常见坑:建集合时草率定了向量维度或距离度量,数据写进去一大半才发现不对。这两个参数改起来往往意味着重建集合、重新灌数据,成本很高。建集合之前,先问清嵌入模型输出多少维、业务上"像"该用余弦还是点积,再动手。

💡 关键直觉:把 upsert 当成默认的写入方式。它让你的写入逻辑天然幂等,重试不怕、增量更新也顺。除非你有明确的"这个 ID 必须已存在才能改"这类强约束,否则别回到"先查再插"的老路上。

四、CRUD 生命周期

下面这张图把建、插、查、改、删五件事画成一个环。新建集合是起点,数据在里面经历写入、读取、更新、删除,最终可能被整体清空,一个生命周期就此走完。

四、CRUD 生命周期

这五件事里,建是起点,插和改共享 upsert 这一条通道,读分三条路,删有逻辑和物理两层。理解了这张环,你就理解了向量数据从生到死的完整路径。

五、数据建模与 ID 策略

CRUD 好不好用,一半取决于数据模型设计得对不对。点 ID 是第一道选择:用自增整数简单直观,适合本地和原型;用 UUID 适合分布式写入、多点并发生成而不冲突,代价是 ID 更长、索引和存储略占空间。这个选择要在一开始定,因为 ID 是后续所有精确操作和 upsert 的锚点,中途改 ID 方案等于重新洗一遍数据。

向量维度是第二道硬约束。它由嵌入模型的输出决定,建集合前就要问清楚模型输出多少维,因为维度一旦定下,集合里所有向量都得对齐,改起来要重建。距离度量是第三道:文本语义多用余弦,向量大小本身有意义时用点积,绝对数值差异敏感时用欧氏。三道选择做对,后面的写入、查询、过滤才顺;做错,往往要付出重建集合的代价。

再补一句关于载荷的设计。载荷不是越多越好,也不是越细越好。将来要参与过滤的字段,要提前规划、建好索引;只用于展示的字段,可以不建索引省资源。把"过滤字段"和"展示字段"分清楚,是数据建模里最划算的一笔投入。

六、CRUD 的节奏:从单条到批量

初学者容易犯的毛病,是把 CRUD 写成"一条一条来"的循环。功能上没错,性能上很糟。写入要批量,前面说过;读取也一样,能一次拿多条的别循环单查。精确取点支持一次传多个 ID,把"查完一条再查下一条"改成"一次查一批",网络往返能省下一个数量级。

删除同理。按 ID 一条条删,和按过滤条件一次删一批,在服务端的工作量和网络开销上差很远。需要清掉一整类数据时,用过滤条件圈定范围、一次删除,比遍历 ID 逐个删干净得多。

还有一个被低估的点:读操作要不要带回向量。向量数据体积不小,很多场景你只需要点 ID 和载荷原文,根本不需要原始向量。把"是否返回向量"关掉,能显著省掉网络传输和反序列化的开销,尤其在结果条数多、向量维度高的时候。按需裁剪返回内容,是 CRUD 从"能跑"到"跑得省"的关键一步。

本节速览

  • 数据模型:集合管维度,点管身份,向量管相似,载荷管过滤,四者各司其职。
  • 建集合:向量维度与距离度量是必须先定死的两个参数,改起来代价高。
  • upsert 语义:有则更新、无则插入,天生幂等,是写入的首选方式。
  • 读分三条路:按 ID 精确取、按向量相似搜、按顺序分页扫,用途不可混淆。
  • 批量优先:批量写入把网络往返次数从十万降到几百,是生产环境的标准做法。
  • 部分更新:只改载荷里某几个字段,省带宽也省掉整点重写的麻烦。
  • 删除分层:逻辑删除快、物理删除慢,存储空间不会在删除后立刻释放。

下一节我们把"读"这条路上最复杂的一部分展开——高级查询接口。同样是搜索,怎么把向量相似度和字段过滤揉在一起,做到又准又快。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U