====== 第六部分:事务(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)与范围查询]]。