====== 第八部分:数据库版本升级 ====== ===== 8.1 版本升级概述 ===== IndexedDB 通过版本号管理数据库结构。当需要添加对象存储、创建索引或修改结构时,必须增加版本号触发升级流程。 ===== 8.1.1 为什么需要版本升级 ===== 应用发布后,需求会不断变化: * 新增功能需要存储新类型的数据 * 发现需要新的查询维度,要添加索引 * 旧的数据结构不再适用,需要迁移 * 需要删除不再使用的对象存储 ===== 8.1.2 升级流程 ===== 1. 调用 indexedDB.open(dbName, newVersion) 2. 如果 newVersion > currentVersion,触发 onupgradeneeded 3. 在 onupgradeneeded 中执行结构变更 4. 升级完成后触发 onsuccess 5. 如果其他页面持有旧版本连接,触发 onblocked ===== 8.2 升级策略 ===== ===== 8.2.1 基本升级 ===== const request = indexedDB.open('MyApp', 2); request.onupgradeneeded = function(event) { const db = event.target.result; const oldVersion = event.oldVersion; console.log(`升级: 版本 ${oldVersion} -> 2`); if (oldVersion < 1) { // 首次创建 const store = db.createObjectStore('users', { keyPath: 'id' }); store.createIndex('emailIndex', 'email', { unique: true }); } if (oldVersion < 2) { // 版本 1 -> 2 的升级 const store = request.transaction.objectStore('users'); store.createIndex('createdAtIndex', 'createdAt', { unique: false }); } }; ===== 8.2.2 多版本递进升级 ===== request.onupgradeneeded = function(event) { const db = event.target.result; const oldVersion = event.oldVersion; const transaction = event.target.transaction; // 从旧版本逐步升级到新版本 if (oldVersion < 1) { // v0 -> v1 db.createObjectStore('users', { keyPath: 'id' }); } if (oldVersion < 2) { // v1 -> v2 const store = transaction.objectStore('users'); store.createIndex('nameIndex', 'name'); } if (oldVersion < 3) { // v2 -> v3 const store = transaction.objectStore('users'); store.createIndex('ageIndex', 'age'); } if (oldVersion < 4) { // v3 -> v4 // 新增对象存储 db.createObjectStore('orders', { keyPath: 'orderId' }); } if (oldVersion < 5) { // v4 -> v5 // 删除旧索引 const store = transaction.objectStore('users'); if (store.indexNames.contains('tempIndex')) { store.deleteIndex('tempIndex'); } } }; ===== 8.3 数据迁移 ===== ===== 8.3.1 字段重命名 ===== request.onupgradeneeded = function(event) { const db = event.target.result; const transaction = event.target.transaction; if (event.oldVersion < 3) { const store = transaction.objectStore('users'); // 迁移:将 'name' 重命名为 'fullName' const cursor = store.openCursor(); cursor.onsuccess = function(e) { const result = e.target.result; if (result) { const user = result.value; if (user.name && !user.fullName) { user.fullName = user.name; delete user.name; result.update(user); } result.continue(); } }; } }; ===== 8.3.2 数据转换 ===== request.onupgradeneeded = function(event) { const db = event.target.result; const transaction = event.target.transaction; if (event.oldVersion < 4) { const store = transaction.objectStore('users'); // 迁移:将年龄从字符串转为数字 store.openCursor().onsuccess = function(e) { const cursor = e.target.result; if (cursor) { const user = cursor.value; if (typeof user.age === 'string') { user.age = parseInt(user.age, 10); cursor.update(user); } cursor.continue(); } }; } }; ===== 8.3.3 对象存储迁移 ===== request.onupgradeneeded = function(event) { const db = event.target.result; const transaction = event.target.transaction; if (event.oldVersion < 5) { // 创建新的对象存储 const newStore = db.createObjectStore('users_v2', { keyPath: 'id' }); newStore.createIndex('emailIndex', 'email', { unique: true }); // 从旧存储迁移数据 const oldStore = transaction.objectStore('users'); oldStore.openCursor().onsuccess = function(e) { const cursor = e.target.result; if (cursor) { const oldUser = cursor.value; // 数据转换 const newUser = { id: oldUser.id, email: oldUser.email, profile: { firstName: oldUser.firstName, lastName: oldUser.lastName, age: oldUser.age }, migratedAt: new Date().toISOString() }; newStore.add(newUser); cursor.continue(); } else { // 迁移完成,删除旧存储 db.deleteObjectStore('users'); } }; } }; ===== 8.4 处理阻塞 ===== ===== 8.4.1 onblocked 事件 ===== const request = indexedDB.open('MyApp', 3); request.onblocked = function(event) { console.warn('数据库升级被阻塞'); console.warn('请关闭其他使用此数据库的标签页'); // 可以提示用户 showNotification('请刷新页面以完成更新'); }; request.onupgradeneeded = function(event) { // 升级逻辑 }; request.onsuccess = function(event) { console.log('数据库升级成功'); }; ===== 8.4.2 优雅处理多个标签页 ===== // 方案:在页面卸载时关闭连接 window.addEventListener('beforeunload', () => { if (db) { db.close(); } }); // 方案:使用 BroadcastChannel 通知其他标签页 const channel = new BroadcastChannel('db_upgrade'); function upgradeDatabase(newVersion) { channel.postMessage({ type: 'upgrading', version: newVersion }); const request = indexedDB.open('MyApp', newVersion); request.onblocked = () => { channel.postMessage({ type: 'blocked' }); }; request.onsuccess = () => { channel.postMessage({ type: 'completed', version: newVersion }); }; } channel.onmessage = (event) => { if (event.data.type === 'upgrading') { // 其他标签页正在升级,主动关闭连接 if (db) { db.close(); db = null; } } }; ===== 8.5 版本管理最佳实践 ===== ===== 8.5.1 版本号策略 ===== // 策略一:线性递增 const DB_VERSION = 5; // 策略二:使用日期作为版本号 const DB_VERSION = 20240423; // YYYYMMDD // 策略三:语义化版本映射 // 应用版本 2.1.3 -> 数据库版本 213 function appVersionToDbVersion(major, minor, patch) { return major * 100 + minor * 10 + patch; } ===== 8.5.2 升级代码组织 ===== const migrations = { 1: function(db, transaction) { db.createObjectStore('users', { keyPath: 'id' }); }, 2: function(db, transaction) { const store = transaction.objectStore('users'); store.createIndex('emailIndex', 'email', { unique: true }); }, 3: function(db, transaction) { const store = transaction.objectStore('users'); store.createIndex('createdAtIndex', 'createdAt'); }, 4: function(db, transaction) { db.createObjectStore('settings', { keyPath: 'key' }); } }; request.onupgradeneeded = function(event) { const db = event.target.result; const transaction = event.target.transaction; const oldVersion = event.oldVersion; for (let version = oldVersion + 1; version <= event.newVersion; version++) { if (migrations[version]) { console.log(`执行迁移: 版本 ${version}`); migrations[version](db, transaction); } } }; ===== 8.6 本章小结 ===== 本章介绍了数据库版本升级: * 通过增加版本号触发 onupgradeneeded * 支持多版本递进升级 * 可以在升级过程中迁移和转换数据 * 处理 onblocked 避免多标签页冲突 * 使用迁移函数组织升级代码 继续阅读 [[part09-advanced|第九部分:高级特性]]。