第 11 / 21 篇

第10章 用事务维护增量更新的一致性

退款政策从三十天改为十四天,如果文档表已经更新而全文索引尚未替换,查询仍可能命中旧数字。向量表、片段坐标和引用摘要也可能暂时属于不同版本。增量索引的核心问题,是怎样让读者看到完整旧版或完整新版。

先准备,后提交

upsert() 先在事务外验证参数、解析输入、生成规范正文、切分并编码。解析器失败或编码器返回数量异常时,数据库尚未变化。这样既保留已有有效投影,也避免在模型计算期间长时间占有写事务。

准备完成后,代码进入 BEGIN IMMEDIATE,重新读取当前文档版本,再决定是否幂等返回、拒绝过期事件或写入新版本。这个重新读取很关键:准备工作可能耗时,不能根据编码前的旧状态假定更新仍然有效。

写入时移除旧片段与对应 FTS 行,更新正文、标题、版本和摘要,再插入新片段、向量与 FTS 词项,最后提交。任意一步抛出异常就回滚。向量存为 JSON,禁止 NaN 序列化;本书没有在事务内部访问外部向量服务。

同一事件可以重试

相同版本、相同规范化摘要且文档仍存活时,返回 unchanged。相同版本却内容不同,或输入版本落后,返回 stale_or_conflicting_revision。这允许消息传递重复,但不把到达时间当成事实先后。

幂等比较针对规范投影,原始 JSON 空格变化不一定触发索引变化。若业务需要保存源对象每次修改,应另设源事件版本;不要要求一个内容摘要同时承担审计原文件与去重索引的职责。

已有文档的内容事件保留当前 ACL。即使旧连接器在正文更新中又带回 Alice,代码也不会因此重新授权。内容版本和权限版本独立,这使“先撤权,再收到新正文”不会悄悄恢复访问。

真实演示怎样验证

演示先缓存第一版退款政策命中,再写入第二版十四天政策。旧命中经 answer() 复核后拒绝,数字“30”的新搜索为空,新摘录包含“14天”。第二版投影摘要记录为 e74729dbab7284e5e7d8a9f4e5aa18882154b79cd3b07e667fdafa27f0bf0f94。

这些检查同时覆盖可检索内容与引用有效性。只断言数据库正文出现十四天,不足以证明旧 FTS 行被清除;只检查新搜索正确,也不足以证明缓存命中已经失效。两个方向都需要运行。

事务并不等于无限并发

SQLite 事务文档 说明,同一数据库可以有多个读事务,但同时只有一个写事务。BEGIN IMMEDIATE 提前尝试取得写入能力,其他写者活动时可能返回忙。本书用进程内可重入锁串行使用单连接,没有构造多进程高并发服务,也没有设置自动重试队列。

扩展成服务时,需要限制写入批次、处理忙错误、观察写入延迟,并保证重试仍遵守版本规则。若采用外部全文与向量系统,单个 SQLite 事务便无法包住所有状态,应引入版本化投影、事件重放与切换协议。这个小实验提供的是一致性目标,而不是所有存储的通用事务方案。