====== 第六部分:事务(Transaction)详解 ====== ===== 6.1 事务概述 ===== 事务是 IndexedDB 中所有数据操作的基本执行单元。理解事务的工作机制,是正确使用 IndexedDB 的关键。事务保证了数据操作的原子性、一致性、隔离性和持久性(ACID)。 ===== 6.1.1 为什么需要事务 ===== 想象一个转账场景:从账户 A 转出 100 元到账户 B。这需要两个操作: 1. 从账户 A 扣除 100 元 2. 向账户 B 增加 100 元 如果第一步成功但第二步失败,数据就会不一致。事务确保这两个操作要么都成功,要么都失败。 ===== 6.1.2 IndexedDB 事务的特性 ===== ^ 特性 ^ 说明 ^ | 原子性 | 事务中的所有操作要么全部成功,要么全部回滚 | | 一致性 | 事务执行前后,数据库保持有效状态 | | 隔离性 | 不同事务之间互不干扰 | | 持久性 | 事务提交后,数据永久保存 | ===== 6.2 事务类型 ===== ===== 6.2.1 readonly(只读事务) ===== 只读事务用于读取数据,可以并发执行: const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); // 多个只读事务可以同时进行 const tx1 = db.transaction(['users'], 'readonly'); const tx2 = db.transaction(['users'], 'readonly'); // tx1 和 tx2 可以同时执行 ===== 6.2.2 readwrite(读写事务) ===== 读写事务用于修改数据,同一对象存储空间同时只能有一个读写事务: const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); // 以下会等待上一个读写事务完成 const tx2 = db.transaction(['users'], 'readwrite'); // tx2 会排队等待 ===== 6.2.3 versionchange(版本变更事务) ===== versionchange 在数据库升级时自动创建,用于修改数据库结构: request.onupgradeneeded = function(event) { const db = event.target.result; const transaction = event.target.transaction; // versionchange 事务 // 可以创建/删除对象存储和索引 db.createObjectStore('newStore', { keyPath: 'id' }); }; ===== 6.3 创建和使用事务 ===== ===== 6.3.1 基本用法 ===== // 单对象存储事务 const transaction = db.transaction('users', 'readwrite'); const store = transaction.objectStore('users'); // 多对象存储事务 const transaction = db.transaction(['users', 'orders'], 'readwrite'); const userStore = transaction.objectStore('users'); const orderStore = transaction.objectStore('orders'); // 使用字符串数组或字符串列表 const transaction = db.transaction(['users', 'orders', 'products'], 'readwrite'); ===== 6.3.2 事务生命周期 ===== const transaction = db.transaction(['users'], 'readwrite'); // 1. 事务开始 console.log('事务已创建'); // 2. 执行操作 const store = transaction.objectStore('users'); store.add({ id: 1, name: '张三' }); store.add({ id: 2, name: '李四' }); // 3. 监听事件 transaction.oncomplete = function(event) { console.log('事务完成,所有操作已提交'); }; transaction.onerror = function(event) { console.error('事务错误,所有操作已回滚:', event.target.error); }; transaction.onabort = function(event) { console.log('事务被中止'); }; // 4. 事务自动提交(当所有请求完成后) ===== 6.3.3 事务的自动提交 ===== IndexedDB 事务在以下情况自动提交: * 所有已发出的请求都已完成(成功或失败) * 事务的事件循环清空 * 没有更多的操作排队 // 错误示例:事务已自动提交 const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); store.add({ id: 1, name: '张三' }); // 等待一段时间后... setTimeout(() => { // 错误!事务可能已经自动提交 store.add({ id: 2, name: '李四' }); // TransactionInactiveError }, 100); ===== 6.4 事务错误处理 ===== ===== 6.4.1 错误传播 ===== 事务中的错误会冒泡到事务对象: const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); // 第一个操作成功 store.add({ id: 1, name: '张三' }); // 第二个操作失败(主键重复) store.add({ id: 1, name: '重复' }); // ConstraintError // 第三个操作不会执行(事务已中止) // store.add({ id: 2, name: '李四' }); transaction.onerror = function(event) { // 这里会收到 ConstraintError console.error('事务因错误而中止:', event.target.error); }; transaction.oncomplete = function() { // 不会触发,因为事务已中止 console.log('事务完成'); }; ===== 6.4.2 防止事务中止 ===== 有时我们希望某个操作失败不影响整个事务: const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); // 捕获单个请求的错误,防止传播到事务 const request1 = store.add({ id: 1, name: '张三' }); request1.onerror = function(event) { event.preventDefault(); // 阻止错误冒泡 console.log('添加失败,但事务继续'); }; // 即使 request1 失败,request2 仍会执行 const request2 = store.add({ id: 2, name: '李四' }); ===== 6.4.3 手动中止事务 ===== const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); store.add({ id: 1, name: '张三' }); // 手动中止事务 if (someCondition) { transaction.abort(); } transaction.onabort = function() { console.log('事务已手动中止,数据未保存'); }; ===== 6.5 多对象存储事务 ===== ===== 6.5.1 跨存储操作 ===== // 创建订单时同时更新用户和库存 async function createOrder(order) { const transaction = db.transaction(['orders', 'users', 'products'], 'readwrite'); const orderStore = transaction.objectStore('orders'); const userStore = transaction.objectStore('users'); const productStore = transaction.objectStore('products'); // 1. 检查用户是否存在 const user = await new Promise((resolve, reject) => { const request = userStore.get(order.userId); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); if (!user) { throw new Error('用户不存在'); } // 2. 检查并扣减库存 for (const item of order.items) { const product = await new Promise((resolve, reject) => { const request = productStore.get(item.productId); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); if (!product || product.stock < item.quantity) { throw new Error(`库存不足: ${item.productId}`); } product.stock -= item.quantity; await new Promise((resolve, reject) => { const request = productStore.put(product); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); } // 3. 创建订单 const orderId = await new Promise((resolve, reject) => { const request = orderStore.add(order); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); // 4. 更新用户订单列表 user.orders = user.orders || []; user.orders.push(orderId); await new Promise((resolve, reject) => { const request = userStore.put(user); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); // 等待事务完成 return new Promise((resolve, reject) => { transaction.oncomplete = () => resolve(orderId); transaction.onerror = () => reject(transaction.error); }); } ===== 6.6 事务并发控制 ===== ===== 6.6.1 并发规则 ===== ^ 事务1 ^ 事务2 ^ 是否并发 ^ | readonly | readonly | 是 | | readonly | readwrite | 是(只读先开始) | | readwrite | readonly | 是(读写先开始) | | readwrite | readwrite | 否(排队) | ===== 6.6.2 死锁避免 ===== 虽然 IndexedDB 事务通常不会死锁,但良好的实践很重要: // 好的实践:总是按相同顺序访问对象存储 function operationA() { return db.transaction(['users', 'orders', 'products'], 'readwrite'); } function operationB() { return db.transaction(['users', 'orders', 'products'], 'readwrite'); } // 两个操作按相同顺序访问,避免死锁 ===== 6.7 本章小结 ===== 本章深入介绍了 IndexedDB 的事务机制: * 三种事务类型:readonly、readwrite、versionchange * 事务自动提交,不能异步后继续使用 * 错误会中止整个事务,可以用 preventDefault() 阻止 * 多对象存储事务保证跨表操作的原子性 * 读写事务之间互斥,只读事务可以并发 继续阅读 [[part07-cursor|第七部分:游标(Cursor)与范围查询]]。