在 SQL 中,我们可以执行 UPDATE table SET field = 'val' WHERE id = 1。但在 IndexedDB 的世界里,原生 API 只提供了 put()。put 的本质是 “全量覆盖” :如果你只传一个字段,整个对象剩下的部分都会丢失。
处理这种“增量更新”时,必须保证原子性(Atomicity) ,尤其是在处理长篇 AI Prompt 时,防止数据在“读取-修改-写入”的过程中被并发操作篡改。
1. 核心原理:Get-Modify-Put 模式
由于 IndexedDB 不支持部分更新,我们必须手动实现一个 patch 方法。关键点在于:获取数据、合并字段、重新存入这三个步骤必须在**同一个事务(Transaction)**中完成。
2. 代码实现:给 PromptDB 增加 patch 方法
我们在之前封装的 PromptDB 基础上,新增一个智能增量更新函数:
JavaScript
/**
* 增量更新指定 ID 的数据
* @param {string} id - 主键
* @param {Object} changes - 需要修改的字段,例如 { title: '新标题' }
*/
async patch(id, changes) {
await this.init();
return new Promise((resolve, reject) => {
// 1. 开启读写事务
const transaction = this.db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);
// 2. 先读取原始数据
const getRequest = store.get(id);
getRequest.onsuccess = () => {
const data = getRequest.result;
if (!data) {
reject(new Error(`ID 为 ${id} 的记录不存在`));
return;
}
// 3. 合并差异 (使用 Object.assign 或 展开运算符)
const updatedData = { ...data, ...changes, updatedAt: Date.now() };
// 4. 写回数据库
const putRequest = store.put(updatedData);
putRequest.onsuccess = () => resolve(updatedData);
putRequest.onerror = () => reject(new Error('增量更新失败'));
};
transaction.onerror = () => reject(new Error('事务异常'));
});
}
3. 为什么必须在同一个“事务”里?
你肯定关心并发控制。
- 场景:AI 正在流式更新
content字段,而用户同时在点击“收藏”按钮(修改isFavorite字段)。 - 风险:如果
get和put分属不同事务,可能会发生“写覆盖”:用户读取了旧数据,修改了收藏状态,此时 AI 更新了内容,用户最后写回时,把 AI 刚更新的内容又覆盖回了旧版。 - 解决:IndexedDB 的事务锁机制能确保在
onsuccess回调完成前,其他写操作必须排队,从而保证了增量操作的原子性。
4. 8 个具体的业务实战场景
| 场景 | patch 传参示例 | 意义 |
|---|---|---|
| 字数统计 | { wordCount: 1500 } | 仅更新统计信息,不触碰数万字的 Prompt 正文 |
| 收藏状态 | { isFavorite: true } | 极速切换,不影响复杂的上下文关联 |
| 流式更新 | { content: currentText } | 在 AI 打字过程中,频繁同步内容,避免丢失进度 |
| 标签修正 | { tags: [...oldTags, '金融'] } | 增量添加标签索引 |
| 错误标记 | { lastError: 'API Timeout' } | 记录异常状态,保留原始请求参数 |
| 读取计数 | { views: data.views + 1 } | 简单的原子累加 |
| 版本回退 | { version: 2, content: history[2] } | 部分字段回滚到历史版本 |
| UI 状态记录 | { isExpanded: false } | 记录卡片折叠状态,不触碰业务数据 |
5. 性能优化:大数据量下的增量更新
如果你处理的是超过 100KB 的大型 Prompt 对象,频繁的 patch 会产生大量的垃圾回收(GC)压力。
- 防抖处理 (Debounce) :对于“用户改了一个词”这种高频操作,建议在内存中缓冲 500ms,再调用一次
patch。 - 字段拆分:如果某个字段(如
chat_history)会无限增长,建议将其拆分为另一个 Object Store,通过 ID 关联,而不是塞在一个超大的对象里不断patch。 - 使用 Proxy:在前端框架中,可以用
Proxy监听对象变化,自动收集changes补丁包,实现自动化增量备份。