事务是 IndexedDB 中所有数据操作的基本执行单元。理解事务的工作机制,是正确使用 IndexedDB 的关键。事务保证了数据操作的原子性、一致性、隔离性和持久性(ACID)。
想象一个转账场景:从账户 A 转出 100 元到账户 B。这需要两个操作:
1. 从账户 A 扣除 100 元 2. 向账户 B 增加 100 元
如果第一步成功但第二步失败,数据就会不一致。事务确保这两个操作要么都成功,要么都失败。
| 特性 | 说明 |
|---|---|
| 原子性 | 事务中的所有操作要么全部成功,要么全部回滚 |
| 一致性 | 事务执行前后,数据库保持有效状态 |
| 隔离性 | 不同事务之间互不干扰 |
| 持久性 | 事务提交后,数据永久保存 |
只读事务用于读取数据,可以并发执行:
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 可以同时执行
读写事务用于修改数据,同一对象存储空间同时只能有一个读写事务:
const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); // 以下会等待上一个读写事务完成 const tx2 = db.transaction(['users'], 'readwrite'); // tx2 会排队等待
versionchange 在数据库升级时自动创建,用于修改数据库结构:
request.onupgradeneeded = function(event) { const db = event.target.result; const transaction = event.target.transaction; // versionchange 事务 // 可以创建/删除对象存储和索引 db.createObjectStore('newStore', { keyPath: 'id' }); };
// 单对象存储事务 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');
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. 事务自动提交(当所有请求完成后)
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);
事务中的错误会冒泡到事务对象:
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('事务完成'); };
有时我们希望某个操作失败不影响整个事务:
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: '李四' });
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('事务已手动中止,数据未保存'); };
// 创建订单时同时更新用户和库存 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); }); }
| 事务1 | 事务2 | 是否并发 |
|---|---|---|
| readonly | readonly | 是 |
| readonly | readwrite | 是(只读先开始) |
| readwrite | readonly | 是(读写先开始) |
| readwrite | readwrite | 否(排队) |
虽然 IndexedDB 事务通常不会死锁,但良好的实践很重要:
// 好的实践:总是按相同顺序访问对象存储 function operationA() { return db.transaction(['users', 'orders', 'products'], 'readwrite'); } function operationB() { return db.transaction(['users', 'orders', 'products'], 'readwrite'); } // 两个操作按相同顺序访问,避免死锁
本章深入介绍了 IndexedDB 的事务机制:
继续阅读 第七部分:游标(Cursor)与范围查询。