
回复
在鸿蒙应用持续迭代的过程中,业务需求变化常常伴随着数据库表结构的变更(如增加新字段、修改字段类型等)。如果处理不当,特别是在跨版本升级时,极易引发数据丢失、应用崩溃等严重生产问题。本文将深入分析这一问题,并提供一套完整的解决方案。
问题场景:
某鸿蒙应用在发布V2.0版本时,为了新增一个功能,需要在原有的用户信息表 user
中增加一个 nickname
(昵称)字段。开发者在本地测试后正常发布。
生产问题:
部分已安装V1.0版本的老用户升级到V2.0后,启动应用时出现闪退。更严重的是,成功启动应用后,部分用户发现原有的账户数据(如收藏列表、浏览记录)全部丢失,变成了一个新用户的状态。
该问题的根源在于数据库版本升级策略的缺失或错误实现。
RDB
商店在打开数据库时,如果指定的版本号 (version
) 比当前数据库的版本号高,会自动触发 upgrade
操作。然而,如果没有为upgrade
显式地编写迁移逻辑,数据库框架会默认采用“删除旧表,创建新表”的策略。这就是导致用户数据被清空的直接原因。onUpgrade
回调中只写了创建新表的SQL(CREATE TABLE IF NOT EXISTS ...
),而没有处理旧数据的迁移。例如:onUpgrade: (db, oldVersion, newVersion) => {
// 只有创建新表的语句,没有数据迁移逻辑
db.executeSql("CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY, name TEXT, nickname TEXT)");
// 此时,老表已经被丢弃,其中的数据全部丢失!
}
v1
直接到 v2
,也可能是从 v1
到 v3
,或者从 v2
回退到 v1
。升级脚本如果没有考虑所有可能的升级路径,就会出错。解决此问题的核心思路是:在数据库结构变更时,必须编写安全的数据迁移脚本,将旧数据库中的数据无损地迁移到新结构的数据库中。
onUpgrade
方法中,根据 oldVersion
和 newVersion
的差异,编写一步一步的升级脚本,确保从任何一个历史版本升级到最新版本都能正确执行。ALTER TABLE ... ADD COLUMN
等标准SQL语句来新增字段,避免重建表。ALTER
语句完成的变更(如修改字段类型、删除字段),需要创建临时表,将旧表数据导入临时表,删除旧表,再创建新表,最后将数据从临时表导回新表。以下是在鸿蒙应用中正确实现数据库升级的代码示例。
1. 定义数据库版本常量
export const DB_CONFIG = {
NAME: 'myApp.db',
VERSION_1: 1, // 初始版本
VERSION_2: 2, // 新增nickname字段
CURRENT_VERSION: 2
};
2. 正确的onUpgrade实现
import { relationalStore } from '@ohos.data.relationalStore';
import { DB_CONFIG } from './DatabaseUtils';
const STORE_CONFIG = {
name: DB_CONFIG.NAME,
securityLevel: relationalStore.SecurityLevel.S1
};
// 获取RDB连接
relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, db) => {
if (err) {
console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in getting RdbStore.');
// 版本变化时,会触发Upgrade
db.version = DB_CONFIG.CURRENT_VERSION;
db.on('versionChange', (event) => {
// 版本变更事件,通常在这里不处理具体逻辑,但可以监听
console.info(`Version changed from ${event.oldVersion} to ${event.newVersion}`);
});
// 核心:数据库升级处理
db.upgrade((db, oldVersion, newVersion, callback) => {
console.info(`Starting database upgrade from v${oldVersion} to v${newVersion}`);
/* 使用事务保证升级操作的原子性 */
db.beginTransaction()
.then(() => {
// 根据旧版本号,逐步升级
if (oldVersion < 2) {
// 从v1升级到v2:增加nickname字段
console.info('Upgrading database from v1 to v2: ADD COLUMN nickname');
return db.executeSql("ALTER TABLE user ADD COLUMN nickname TEXT");
}
// 未来如果升级到v3,可以在这里添加 else if (oldVersion < 3) {...}
})
.then(() => {
db.commit(); // 提交事务
callback(); // 升级成功,调用callback
console.info('Database upgrade completed successfully.');
})
.catch((err) => {
db.rollback(); // 升级失败,回滚事务
console.error(`Database upgrade failed. Code:${err.code}, message:${err.message}`);
// 回调失败,数据库打开会失败,避免用户使用一个有问题的数据库
throw err;
});
});
});
3. 复杂变更的示例(需要重建表)
假设V3版本需要将表 user
中的 name
字段拆分为 first_name
和 last_name
,无法用 ALTER
完成。