鸿蒙应用开发实战【40】— 数据库版本升级ALTER TABLE策略
鸿蒙应用开发实战【40】— 数据库版本升级ALTER TABLE策略
本文是「号码助手全栈开发系列」第 40 篇,持续更新中…
开源社区:https://openharmonycrossplatform.csdn.net
前言
数据库从第 1 版迭代到第 N 版,表结构不可避免要演进——新增字段、修改默认值、添加索引甚至拆分表。HarmonyOS RDB 通过 version + onUpgrade 回调来管理版本迁移。
本篇涵盖:版本号管理机制、ALTER TABLE ADD COLUMN 语法、onUpgrade 升级流程、新增字段默认值策略、索引增量创建、数据迁移兼容性、降级防护、升级前后的完整性校验。

一、RDB 版本号管理机制
1.1 StoreConfig 中的版本
HarmonyOS 的 relationalStore.StoreConfig 没有直接传入版本号的字段,版本由 RdbStore.version 属性管理:
import relationalStore from '@ohos.data.relationalStore';
const DB_CONFIG: relationalStore.StoreConfig = {
name: 'number_assistant.db',
securityLevel: relationalStore.SecurityLevel.S2
};
// 初始化后获取版本
const store = await relationalStore.getRdbStore(context, DB_CONFIG);
console.info('当前数据库版本:', store.version);
1.2 版本升级流程
store.version 在首次创建时为 0。我们在 init() 中手动管理版本:
async init(context: UIAbilityContext): Promise<void> {
this.store = await relationalStore.getRdbStore(context, DB_CONFIG);
await this.upgrade(this.store, this.store.version, DB_VERSION);
this.store.version = DB_VERSION; // 写入新版本
}
关键规则:
store.version是数字,只能递增- 每次升级时比对
fromVersion(当前)和toVersion(期望) - 如果
fromVersion已 ≥toVersion,跳过升级
二、ALTER TABLE ADD COLUMN
2.1 语法
HarmonyOS RDB 仅支持 ALTER TABLE ADD COLUMN,不支持修改列名、删除列或修改列类型:
ALTER TABLE table_name ADD COLUMN column_name data_type DEFAULT default_value;
2.2 实战:给 app_bindings 添加 expire_date 字段
假设 v1 → v2 需要新增一个过期日期字段:
const DB_VERSION = 2;
private async upgrade(store: relationalStore.RdbStore, fromVersion: number, toVersion: number): Promise<void> {
// v1 初始化
if (fromVersion < 1) {
// ... 建表 SQL ...
}
// v2 升级:新增 expire_date
if (fromVersion < 2) {
const alterSql = `ALTER TABLE app_bindings ADD COLUMN expire_date INTEGER NOT NULL DEFAULT 0`;
try {
await store.executeSql(alterSql);
hilog.info(0x0000, TAG, 'v2 升级完成:新增 expire_date');
} catch (e) {
hilog.error(0x0000, TAG, 'v2 升级失败: %{public}s', JSON.stringify(e));
throw new Error('数据库升级失败(→v2):' + JSON.stringify(e));
}
// 同时添加新索引
await store.executeSql(
`CREATE INDEX IF NOT EXISTS idx_ab_expire ON app_bindings(expire_date)`
);
}
}
2.3 添加索引的最佳时机
索引不应写在建表阶段之后就无法变更。升级时也可以增量添加:
if (fromVersion < 2) {
// 复合索引:在已有索引基础上补充
await store.executeSql(
`CREATE INDEX IF NOT EXISTS idx_ab_card_status ON app_bindings(card_id, status)`
);
}
使用 IF NOT EXISTS 保证幂等性——无论执行多少次都不会报错。
三、实体模型同步更新
3.1 接口扩展
数据库升级后,TypeScript 接口也要同步新增字段:
export interface AppBindingEntity {
id?: number;
app_name: string;
icon_key: string;
category: string;
card_id: number;
status: string;
remark: string;
source: string;
created_at: number;
updated_at: number;
// v2 新增
expire_date?: number; // 可选,兼容旧数据
}
3.2 DAO 层兼容
新增字段使用可选属性 (?),这样从旧版数据库查询出来的数据不会有类型错误。
写入时提供默认值:
async create(data: AppBindingEntity): Promise<number> {
const now = Date.now();
const entity = {
...data,
expire_date: data.expire_date ?? 0 // 默认为 0(永不过期)
};
// ...
}
四、数据迁移策略
4.1 纯新增字段(推荐)
只做 ADD COLUMN + 默认值,旧数据自动填充默认值。
ALTER TABLE app_bindings ADD COLUMN expire_date INTEGER NOT NULL DEFAULT 0;
所有已有行的 expire_date 自动变为 0。
4.2 字段重命名(有限支持)
RDB 不支持直接 RENAME COLUMN。替代方案:
ALTER TABLE ADD COLUMN new_columnUPDATE table SET new_column = old_column- 在 Entity 层标记旧字段为
@deprecated
// v3 升级:phone → phone_number
if (fromVersion < 3) {
await store.executeSql(
`ALTER TABLE cards ADD COLUMN phone_number TEXT NOT NULL DEFAULT ''`
);
await store.executeSql(
`UPDATE cards SET phone_number = phone`
);
// 旧字段保留,代码层不再使用
}
4.3 数据清洗
升级时还可以执行数据清洗:
if (fromVersion < 4) {
// 将旧版的状态中文名标准化
await store.executeSql(
`UPDATE app_bindings SET status = '使用中' WHERE status = 'active'`
);
await store.executeSql(
`UPDATE app_bindings SET status = '已停用' WHERE status = 'disabled'`
);
}
五、完整的升级链路
把多个版本升级串联成一个完整的 switch/case 链:
const DB_VERSION = 5;
private async upgrade(store: relationalStore.RdbStore, fromVersion: number, toVersion: number): Promise<void> {
// v1:建表
if (fromVersion < 1) {
await store.executeSql(`CREATE TABLE IF NOT EXISTS cards (...)`);
await store.executeSql(`CREATE TABLE IF NOT EXISTS app_bindings (...)`);
fromVersion = 1;
}
// v2:添加 expire_date
if (fromVersion < 2) {
await store.executeSql(`ALTER TABLE app_bindings ADD COLUMN expire_date INTEGER NOT NULL DEFAULT 0`);
await store.executeSql(`CREATE INDEX IF NOT EXISTS idx_ab_expire ON app_bindings(expire_date)`);
fromVersion = 2;
}
// v3:phone → phone_number 迁移
if (fromVersion < 3) {
await store.executeSql(`ALTER TABLE cards ADD COLUMN phone_number TEXT NOT NULL DEFAULT ''`);
await store.executeSql(`UPDATE cards SET phone_number = phone`);
fromVersion = 3;
}
// v4:新增 sync_status 表
if (fromVersion < 4) {
await store.executeSql(`CREATE TABLE IF NOT EXISTS sync_meta (...)`);
fromVersion = 4;
}
// v5:清洗脏数据
if (fromVersion < 5) {
await store.executeSql(`UPDATE cards SET color = 'blue' WHERE color NOT IN ('blue','green','pink','orange','purple')`);
fromVersion = 5;
}
hilog.info(0x0000, TAG, '数据库升级完成: v%{public}d → v%{public}d', fromVersion, toVersion);
}
六、降级防护
用户设备上可能安装了旧版本应用,打开新版数据库后又回退到旧版。此时 store.version 可能 > 应用的 DB_VERSION。
private async upgrade(store: relationalStore.RdbStore, fromVersion: number, toVersion: number): Promise<void> {
if (fromVersion > toVersion) {
hilog.warn(0x0000, TAG, '检测到更高版本的数据库(v%{public}d),跳过降级', fromVersion);
return; // 不降级,保持兼容
}
// ...
}
核心原则:数据库只向前兼容。低版本 App 遇到高版本数据库,只读不写新增字段,不会崩溃。
七、升级前校验与回滚
7.1 升级前校验
private async preUpgradeCheck(store: relationalStore.RdbStore): Promise<void> {
// 检查数据库是否可写
await store.executeSql('SELECT 1');
// 检查关键表是否存在
const tables = ['cards', 'app_bindings'];
for (const t of tables) {
const result = await store.querySql(`SELECT name FROM sqlite_master WHERE type='table' AND name='${t}'`);
if (!result.rowCount) {
throw new Error(`关键表 ${t} 不存在`);
}
result.close();
}
}
7.2 事务回滚
升级失败时,整个 executeSql 链会自动回滚吗?不会。HarmonyOS RDB 的每条 executeSql 默认是自动提交的。我们需要手动开启事务:
if (fromVersion < 2) {
try {
await store.beginTransaction();
await store.executeSql('ALTER TABLE ...');
await store.executeSql('CREATE INDEX ...');
await store.commit();
} catch (e) {
await store.rollback();
throw new Error('v2 升级失败,已回滚');
}
}
八、单元测试
import { describe, it, expect } from '@ohos/hypium';
describe('DatabaseUpgradeTest', () => {
it('版本号递增', 0, () => {
expect(DB_VERSION).assertGreaterThan(0);
expect(DB_VERSION).assertEqual(5);
});
it('ALTER TABLE 新增字段', 0, async () => {
const store = DatabaseService.getInstance().getStore();
await store.executeSql(`ALTER TABLE app_bindings ADD COLUMN test_col TEXT NOT NULL DEFAULT ''`);
// 验证字段存在
const result = await store.querySql(`PRAGMA table_info(app_bindings)`);
// ...
});
});
小结
| 要点 | 说明 |
|---|---|
| 版本管理 | store.version 递增,由 upgrade() 手动控制 |
| ALTER TABLE | 仅支持 ADD COLUMN,不支持 DROP/RENAME COLUMN |
| 默认值 | NOT NULL + DEFAULT 保证旧数据兼容 |
| 索引 | 升级时用 IF NOT EXISTS 增量创建 |
| 降级防护 | fromVersion > toVersion 时跳过 |
| 事务保护 | 复杂升级用 beginTransaction + commit 包裹 |
| 实体同步 | TypeScript 接口新增可选属性 |
8.1 ALTER TABLE 语法参考
| 操作 | 语法 | HarmonyOS RDB 支持 |
|---|---|---|
| 新增列 | ALTER TABLE t ADD COLUMN c type DEFAULT v |
✅ 支持 |
| 重命名列 | ALTER TABLE t RENAME COLUMN old TO new |
❌ 不支持 |
| 删除列 | ALTER TABLE t DROP COLUMN c |
❌ 不支持 |
| 重命名表 | ALTER TABLE t RENAME TO new_t |
✅ 支持 |
8.2 版本升级历史
| 版本 | 变更内容 | SQL 操作 |
|---|---|---|
| v1 | 建表(card / app_binding / sms_candidate) | CREATE TABLE |
| v2 | 新增 expire_date 字段 + 索引 | ADD COLUMN + CREATE INDEX |
| v3 | phone → phone_number 迁移 | ADD COLUMN + UPDATE |
| v4 | 新增 sync_meta 表 | CREATE TABLE |
| v5 | 清洗脏数据 | UPDATE … SET |
至此,数据库全系列(26-40)全部完成!从 RDB 概念、DatabaseService 初始化、三个 Entity/DAO 设计、批量操作、错误处理、查询优化、数据统计、导出备份、导入恢复到版本升级,构成了完整的持久化链路。
从第 41 篇开始,我们进入核心页面开发阶段——用 ArkUI 组件把数据模型转化为可交互的界面。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- openHarmony 跨平台社区:https://openharmonycrossplatform.csdn.net
- HarmonyOS 官方文档:https://developer.huawei.com/consumer/cn/doc/
- HarmonyOS RDB开发指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/data-persistence-by-rdb
- HarmonyOS hilog日志:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/hilog
更多推荐


所有评论(0)