IndexedDB - 事务
IndexedDB - 理解事务
Section titled “IndexedDB - 理解事务”事务是 IndexedDB 的核心。它们是读取或写入数据的唯一方式,并提供关键的安全性和一致性保证。可以将事务想象成数据库操作的保护气泡。气泡内的一切要么一起成功,要么如果其中一项失败,整个气泡就会破裂,数据库会恢复到什么都没发生过的状态。这个特性称为原子性。
事务的关键概念
Section titled “事务的关键概念”- 范围(Scope): 事务可以访问的对象存储区集合。你在创建事务时定义此项(例如,
db.transaction(['store1', 'store2']))。只请求你需要的存储区是性能最佳实践。 - 模式(Mode): 决定事务允许执行的操作。
- readonly:(默认)只能读取数据。这是最快的模式,因为多个
readonly事务可以并发运行而不会相互阻塞。 - readwrite:可以读取、创建、更新和删除数据。这些事务是独占的;为了防止冲突,一个对象存储区一次只能有一个
readwrite事务处于活动状态。 - versionchange:一种特殊的独占模式,仅在
onupgradeneeded事件中运行。它是唯一可以修改数据库结构(其架构)的地方,例如创建或删除对象存储区和索引。 - 生命周期(自动提交): 只要你对事务发出新请求,事务就处于活动状态。一旦你的代码块完成且没有更多待处理的请求,事务将自动尝试提交其更改。你无法对已提交或已中止的事务发出新请求。
示例:原子操作
Section titled “示例:原子操作”想象一个场景,你需要将一个机器人从一个项目转移到另一个项目。这需要两次写入操作:更新旧的项目记录和新的项目记录。事务确保这些操作原子性地发生。
首先,一个成功的事务:
async function transferBot(db, botId, fromProjectId, toProjectId) { // 在 'projects' 存储区上启动 'readwrite' 事务 const tx = db.transaction('projects', 'readwrite');
try { console.log(`正在将机器人 ${botId} 从项目 ${fromProjectId} 转移到 ${toProjectId}...`);
// 获取两个项目记录 const [fromProject, toProject] = await Promise.all([ tx.store.get(fromProjectId), tx.store.get(toProjectId) ]);
// 执行逻辑 fromProject.bots = fromProject.bots.filter(id => id !== botId); toProject.bots.push(botId);
// 将两项更改写回数据库 await Promise.all([ tx.store.put(fromProject), tx.store.put(toProject) ]);
await tx.done; // 显式等待事务完成 console.log('转移成功!');
} catch (error) { console.error('转移失败!正在回滚更改。', error); // 事务在出错时会自动中止,无需 tx.abort() }}示例:失败(已中止)的事务
Section titled “示例:失败(已中止)的事务”现在,让我们看看如果事务的一部分失败会发生什么。我们将通过尝试添加一个已存在键的记录来模拟错误。这将导致整个事务中止,并且第一次成功的 put 操作将被回滚。
async function failedTransactionDemo(db) { const tx = db.transaction('employees', 'readwrite'); console.log('\n--- 正在演示一个失败的事务 ---'); try { // 第一次操作将暂时成功 await tx.store.put({ id: 5, name: 'Frank', department: 'Security' }); console.log('已临时添加 Frank。');
// 第二次操作将失败,因为 ID 为 1 的员工已存在 await tx.store.add({ id: 1, name: 'Duplicate Alice', department: 'Error' });
await tx.done; } catch (error) { console.error(`事务按预期中止: ${error.name}`); }
// 让我们检查数据库。Frank 不应该在那里。 const frank = await db.get('employees', 5); console.log('中止事务后 Frank 是否在数据库中?', frank); // 应该为 undefined}事务的最佳实践
Section titled “事务的最佳实践”- 快速操作: 不要长时间保持事务开放。如果事务保持空闲状态,浏览器可能会使其超时并中止。
- 不要
await非 IDB 操作: 永远不要在活动事务内部await网络请求(如fetch)或长时间的setTimeout。事务很可能在你的其他操作完成之前关闭,导致后续的数据库请求失败。 - 具体明确: 除非你绝对需要写入,否则始终请求
readonly模式。它的性能更高。 - 处理错误: 始终将你的事务逻辑包装在
try...catch块中,以优雅地处理潜在的错误,例如ConstraintError或写入失败。