news 2026/7/11 9:38:15

26 数据库版本升级策略:从 v1 到 v2 的增量迁移实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
26 数据库版本升级策略:从 v1 到 v2 的增量迁移实践

26 数据库版本升级策略:从 v1 到 v2 的增量迁移实践

前言

图:26 数据库版本升级策略:从 v1 到 v2 的增量迁移实践 运行效果截图(HarmonyOS NEXT)

在应用迭代过程中,数据库表结构往往会发生变化——新增字段、创建新表、修改索引。如何在不丢失用户已有数据的前提下优雅地完成数据库迁移,是每个应用开发者必须掌握的技能。

本文以"鹿鹿·笔迹心理分析"项目的 [DatabaseService.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/DatabaseService.ets) 中从 v1 到 v2 的实战迁移为例,深入解析鸿蒙 RDB 的版本管理机制和增量升级策略。

鸿蒙官方·数据库版本管理:developer.huawei.com
项目源码仓库:harmony-app GitHub

图:鸿蒙 RDB 版本升级策略——增量迁移从 v1 到 vN

否,版本已是最新

是,需要升级

否,currentVersion == 2

应用启动 init

store.version < DB_VERSION?

直接使用 RdbStore

调用 upgrade 方法

currentVersion == 1?

执行 v1→v2 迁移脚本

ALTER TABLE / CREATE TABLE

store.version = 2

执行 v2→v3 脚本

还需继续升级?

一、版本管理的核心机制

1.1 RdbStore 的版本属性

在鸿蒙 RDB 中,每打开一个数据库,RdbStore实例都会暴露一个version属性:

constcurrentVersion=store.version

各阶段的 version 值:

场景store.version 值说明
新创建的数据库0磁盘上还没有版本号
v1 版本的数据库1upgrade(from=0,to=1) 执行后 v1 = 1
v2 版本的数据库2upgrade(from=1,to=2) 执行后 v2 = 2
已更新过代码但未赋值的仍为 1需要显式 store.version = DB_VERSION

关键点store.version读取的是数据库中记录的版本号,而非代码中声明的DB_VERSION。正确做法是逐段升级后再赋值。

1.2 版本检测与升级入口

asyncinit(context:common.UIAbilityContext):Promise<void>{this.store=awaitrelationalStore.getRdbStore(context,DB_CONFIG)// 版本检测:store.version 从磁盘读取,DB_VERSION 是代码中的最新版awaitthis.upgrade(this.store,this.store.version,DB_VERSION)// 写入最新版本号到数据库this.store.version=DB_VERSION}

二、upgrade 方法的设计模式

2.1 逐段升级的结构

privateasyncupgrade(store:relationalStore.RdbStore,fromVersion:number,toVersion:number):Promise<void>{// 从 v0 到 v1:完整建表if(fromVersion<1){// 执行 v1 的 DDL// ...}// 从 v1 到 v2:新增字段if(fromVersion<2){// 执行 v2 的 DDL// ...}// 后续版本:追加 if (fromVersion < 3) { ... }// if (fromVersion < 4) { ... }}

增量升级的核心思想:每一个版本升级都是"添加"操作——在现有结构上增加新表或新列,确保所有老版本的用户都能平滑升级到最新版。

2.2 版本升级路径示例

// 场景 A:v0 → v2(新用户)// 当前版本:0// 执行:fromVersion < 1 → 建全部 5 张表// 执行:fromVersion < 2 → 添加 3 个新列// 最终版本:实际版本 2// 场景 B:v1 → v2(老用户)// 当前版本:1// 跳过:fromVersion < 1 → 表已存在,不执行// 执行:fromVersion < 2 → 添加 3 个新列// 最终版本:2// 场景 C:v0 → v1 → v2 → v3(跨越多个版本)// 每个 `if (fromVersion < N)` 都会依次执行,确保不遗漏任何中间版本

三、v1 阶段:完整建表

3.1 DDL 语句

fromVersion < 1分支负责初始版本的所有建表语句:

if(fromVersion<1){constddls:string[]=[// 5 张表的 CREATE TABLE`CREATE TABLE IF NOT EXISTS user (...)`,`CREATE TABLE IF NOT EXISTS archive (...)`,`CREATE TABLE IF NOT EXISTS handwriting (...)`,`CREATE TABLE IF NOT EXISTS report (...)`,`CREATE TABLE IF NOT EXISTS relation (...)`,// 索引`CREATE INDEX IF NOT EXISTS idx_handwriting_archive ON handwriting(archive_id, created_at DESC)`,`CREATE INDEX IF NOT EXISTS idx_report_handwriting ON report(handwriting_id)`]for(constsqlofddls){try{awaitstore.executeSql(sql)}catch(e){hilog.error(0x0000,'DatabaseService','建表 SQL 执行失败: %s',JSON.stringify(e))thrownewError('建表 SQL 执行失败: '+JSON.stringify(e))}}}

3.2 异常处理

关键做法:建表失败时立即抛出异常,阻止应用继续运行。因为数据库是应用的基础设施,表结构不完整时后续所有操作都会失败。尽早报错比在用户使用中途崩溃更好。

提示:CREATE TABLE IF NOT EXISTS保证了幂等性——多次执行不会报错,也不会重复建表。

四、v2 阶段:字段新增

4.1 迁移需求

在 v2 版本中,需要为 handwriting 表增加 3 个字段:

  • device_type:设备类型(如 ‘Camera’, ‘Tablet’)
  • word_count:字数统计
  • write_duration:书写时长
if(fromVersion<2){constmigrates:string[]=['ALTER TABLE handwriting ADD COLUMN device_type TEXT DEFAULT \'\'','ALTER TABLE handwriting ADD COLUMN word_count INTEGER DEFAULT 0','ALTER TABLE handwriting ADD COLUMN write_duration INTEGER DEFAULT 0']for(constsqlofmigrates){try{awaitstore.executeSql(sql)}catch(e){// 列已存在时静默忽略hilog.error(0x0000,'DatabaseService','迁移 SQL 执行失败: %s',JSON.stringify(e))// 不抛出异常:列已存在时可安全继续}}}

4.2 异常处理的差异

阶段异常处理理由
v1 建表throw new Error(...)终止应用核心结构缺失,无法正常运行
v2 迁移日志记录,不抛出异常列可能已存在(调试期反复创建),静默忽略

五、字段新增的限制与策略

5.1 SQLite ALTER TABLE 限制

SQLite 对ALTER TABLE的支持非常有限:

-- ✅ 支持ALTERTABLEhandwritingADDCOLUMNdevice_typeTEXTDEFAULT''-- ❌ 不支持ALTERTABLEhandwritingDROPCOLUMNdevice_type-- SQLite < 3.35.0-- ❌ 不支持ALTERTABLEhandwritingRENAMECOLUMNdevice_typeTOdevice-- SQLite < 3.25.0-- ❌ 不支持ALTERTABLEhandwritingALTERCOLUMNdevice_typeSETNOTNULL

5.2 ALTER TABLE ADD COLUMN 的注意事项

// ✅ 正确:指定 DEFAULT 值'ALTER TABLE handwriting ADD COLUMN device_type TEXT DEFAULT \'\'''ALTER TABLE handwriting ADD COLUMN word_count INTEGER DEFAULT 0''ALTER TABLE handwriting ADD COLUMN write_duration INTEGER DEFAULT 0'// ❌ 错误:NOT NULL 但无 DEFAULT// (SQLite 要求:对已有表新增 NOT NULL 列,必须指定 DEFAULT)'ALTER TABLE handwriting ADD COLUMN device_type TEXT NOT NULL'// 会失败!

5.3 复杂的表结构变更

如果需要进行 SQLite 不直接支持的变更(如删除列、修改列类型),需要采用重建表的策略:

// 复杂变更策略(示例,非本项目)if(fromVersion<3){awaitstore.executeSql('BEGIN TRANSACTION')// 1. 创建新表awaitstore.executeSql('CREATE TABLE handwriting_new (...)')// 2. 迁移数据awaitstore.executeSql('INSERT INTO handwriting_new SELECT ... FROM handwriting')// 3. 删除旧表awaitstore.executeSql('DROP TABLE handwriting')// 4. 重命名新表awaitstore.executeSql('ALTER TABLE handwriting_new RENAME TO handwriting')// 5. 重建索引awaitstore.executeSql('CREATE INDEX ...')awaitstore.executeSql('COMMIT')}

六、版本号管理规范

6.1 版本号的意义

// DatabaseService.ets 头部constDB_VERSION=2// 每次表结构变化时 +1// 版本号历史:// v1 = 初始版本:5 张表 + 2 个索引// v2 = handwriting 增加 device_type / word_count / write_duration

6.2 版本号更新流程

需求变更 → 需要修改表结构 ↓ ① 在 DatabaseService 中修改 DB_VERSION const DB_VERSION = 3 ↓ ② 在 upgrade() 方法末尾追加新分支 if (fromVersion < 3) { ... } ↓ ③ 更新注释说明版本变化 // v3 = handwriting 新增 xxx 字段 ↓ ④ 更新 Models.ets 接口定义 interface HandwritingEntity { xxx?: type } ↓ ⑤ 更新对应 DAO 的 COLS 数组和 parseRow 方法

七、调试技巧

7.1 日志追踪

// DatabaseService.etshilog.info(0x0000,'DatabaseService','RDB 初始化完成 v%d',DB_VERSION)// upgrade 阶段hilog.info(0x0000,'DatabaseService','建表完成 → v1')// v1→v2hilog.info(0x0000,'DatabaseService','迁移完成 → v2')

典型日志输出:

// 首次安装(v0 → v2) [DatabaseService] 建表完成 → v1 [DatabaseService] 迁移完成 → v2 [DatabaseService] RDB 初始化完成 v2 // 老版本升级(v1 → v2) [DatabaseService] 迁移完成 → v2 [DatabaseService] RDB 初始化完成 v2 // 已升级到 v2,再次启动(无变更) [DatabaseService] RDB 初始化完成 v2

7.2 版本号重置(开发阶段)

开发调试时,可能需要重置数据库:

// 方案 A:删除数据库文件(需要在真机上清除应用数据)// 设置 → 应用管理 → 鹿鹿 → 删除数据// 方案 B:卸载重装// adb uninstall com.example.lulu// 方案 C:递增 DB_VERSION(不推荐在正式版使用)constDB_VERSION=999// 所有已有数据会留在旧数据库中

八、向前兼容

8.1 v2 字段的兼容处理

由于 v2 新增的字段都有DEFAULT值,旧版本 DAO 的 COLS 数组也能正常查询:

// HandwritingDao.ets — COLS 数组包含新字段constCOLS:string[]=['id','archive_id','source','image_path','ocr_text','feature_json','energy_score','device_type',// ← v2 新增'word_count',// ← v2 新增'write_duration',// ← v2 新增'created_at']

8.2 增量升级的优势

特性增量升级全量重建
用户数据✅ 保留❌ 丢失
执行速度✅ 快(只需执行迁移 SQL)❌ 慢(重建所有数据)
代码复杂度✅ 低(追加 if 分支)❌ 高(需要备份恢复)
风险✅ 低❌ 高

总结

本文深入解析了鸿蒙 RDB 数据库版本升级的完整方案:

  1. 版本检测store.version读取磁盘版本,与代码中的DB_VERSION对比
  2. 增量升级if (fromVersion < N)逐段执行迁移代码
  3. v0→v1 建表:使用CREATE TABLE IF NOT EXISTS+ 索引
  4. v1→v2 迁移ALTER TABLE ADD COLUMN+ DEFAULT 值
  5. 异常策略:建表失败终止应用,迁移失败静默忽略
  6. 向前兼容:新增字段默认值、DAO 包含所有列、Entity 接口可选中

核心原则:每个版本升级都是"添加"操作,不修改已有数据。用户从任何旧版本都能平滑升级到最新版。

下一篇文章将深入RdbPredicates 条件查询——鸿蒙 ORM 风格的查询条件构建。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


参考资源:

  • relationalStore 版本管理
  • SQLite ALTER TABLE
  • [DatabaseService 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/DatabaseService.ets)
  • CREATE TABLE IF NOT EXISTS
  • 鸿蒙数据持久化指南
  • RdbStore 执行 SQL
  • 鸿蒙开发文档
  • [HandwritingDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/HandwritingDao.ets)
  • [Models.ets 实体定义](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/Models.ets)

你遇到过数据库升级问题吗?

  • A. 升级后字段缺失,数据读取崩溃
  • B. 版本号遗漏递增,升级逻辑未触发
  • C. 旧版本数据格式与新版本不兼容
  • D. 目前没有遇到过问题

欢迎在评论区分享你的数据库迁移经历!

下一篇文章将介绍RdbPredicates 条件查询——如何用链式 API 构建精确的查询条件。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/11 9:36:51

SpringAI 2.0与MCP协议:Java开发者AI应用开发实战指南

如果你正在寻找一份真正能让你从零开始掌握 Java SpringAI 2.0 开发实战的完整教程&#xff0c;那么这篇文章就是为你准备的。2026年&#xff0c;AI应用开发已经不再是少数专家的专利&#xff0c;而是每个Java开发者都应该掌握的核心技能。但现实是&#xff0c;很多教程要么停…

作者头像 李华
网站建设 2026/7/11 9:35:42

基于大语言模型的互动小说AI对话生成技术实践

这次我们来看一个有趣的AI应用场景——如何用技术手段实现小说情节的实时生成和互动体验。《云云穿书》这个标题暗示了一个穿书题材的互动故事&#xff0c;结合AI技术可以实现角色对话、情节推进和个性化内容生成。 这种技术最核心的价值在于能够根据用户输入实时生成符合角色…

作者头像 李华
网站建设 2026/7/11 9:35:00

Python requests.Session 实战:17k.com 模拟登录与书架数据抓取 3 步解析

Python requests.Session 实战&#xff1a;17k.com 模拟登录与书架数据抓取 3 步解析 在当今数据驱动的时代&#xff0c;能够高效获取和处理网络数据已成为开发者必备的核心技能之一。对于需要登录才能访问的内容&#xff0c;传统的爬虫技术往往显得力不从心。本文将深入探讨如…

作者头像 李华
网站建设 2026/7/11 9:34:31

VCS 编译排错指南:5类常见 SV/UVM 语法错误定位与修复

VCS 编译排错实战&#xff1a;SystemVerilog/UVM 五大语法陷阱深度解析 当VCS的报错信息像瀑布一样冲刷终端窗口时&#xff0c;每个数字IC验证工程师都经历过那种头皮发麻的瞬间。本文将从错误模式而非简单报错代码的角度&#xff0c;带您穿透表象直达问题本质。不同于零散的报…

作者头像 李华
网站建设 2026/7/11 9:32:13

游戏出海必看!APP推广必懂的ASO优化秘籍

无论你如何通过游戏盈利&#xff0c;获得更多下载量始终是增加收入的好方法&#xff0c;而自然增长&#xff08;无需为每次下载付费&#xff09;是实现这一目标的绝佳途径。在 App Store 和 Google Play 上的自然增长&#xff0c;来自于在搜索结果中的曝光度。这背后有着一套相…

作者头像 李华