====== 第八部分:数据库版本升级 ======
===== 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|第九部分:高级特性]]。