HarmonyOS 7 API 26 RDB 表结构升级检查图

HarmonyOS 7 / API 26 RDB 表结构升级怎么防翻车:版本号、事务迁移和回滚检查怎么拆

RDB 表结构升级最怕两件事:开发环境没问题,老用户一升级就打不开;迁移跑到一半失败,库里留下半成品。HarmonyOS 7.0.0 / API 26 项目里,我会把 RDB 升级当成发布前必须验证的稳定性问题,而不是临时改几条 SQL。

这篇只拆一个问题:表结构升级时,版本号、事务迁移和回滚检查怎么一起做,才能避免老数据被改坏。

运行环境和检查范围

项目 取值
系统版本 HarmonyOS 7.0.0,满足 HarmonyOS 5.0.0 及以上范围
API 版本 API 26
工程模型 Stage 模型
开发语言 ArkTS
数据能力 RDB 本地关系型数据库
验证目标 老版本数据升级后可用,迁移失败不留下半成品

问题一般怎么发生

坏例子通常是发现字段不够用了,就直接加一列:

await rdbStore.executeSql('ALTER TABLE recipe ADD COLUMN cover TEXT')
await rdbStore.executeSql('UPDATE recipe SET cover = defaultCover')

如果第二句失败,表已经被改了,数据却没补齐。更麻烦的是,代码里没有记录当前库版本,下一次启动还可能重复执行。

先用脚本把坏迁移挡住

我用三个场景做检查:没有版本号的坏迁移、正常升级、升级中失败回滚。

const cases = [
  { name: 'bad-rdb-migration', hasSchemaVersion: false, usesTransaction: false, hasRollbackCheck: false },
  { name: 'good-v1-to-v2', hasSchemaVersion: true, usesTransaction: true, hasRollbackCheck: true },
  { name: 'good-rollback', hasSchemaVersion: true, usesTransaction: true, hasRollbackCheck: true },
];

function inspect(item) {
  const errors = [];
  if (!item.hasSchemaVersion) errors.push('schema version is missing');
  if (!item.usesTransaction) errors.push('migration is not transactional');
  if (!item.hasRollbackCheck) errors.push('rollback check is missing');
  return { ...item, passed: errors.length === 0, errors };
}

本地验证结果是 2 个通过、1 个失败。失败项就是没有版本号、没有事务、没有回滚检查的迁移写法。

{
  "total": 3,
  "passed": 2,
  "failed": 1
}

第一层:先维护 schema 版本号

数据库版本不能靠猜。项目里要有明确的 schemaVersion,启动时先读当前版本,再按版本差异执行迁移。

const TARGET_SCHEMA_VERSION = 2

async function ensureSchema(rdbStore: relationalStore.RdbStore) {
  const current = await readSchemaVersion(rdbStore)
  if (current < 2) {
    await migrateV1ToV2(rdbStore)
  }
  await saveSchemaVersion(rdbStore, TARGET_SCHEMA_VERSION)
}

这里不要把所有升级都塞到一个大函数里。每个版本到下一个版本,单独写一个迁移函数,出问题也好定位。

第二层:迁移必须放进事务

表结构和数据补齐要一起成功。只要中间失败,就不要留下半成品。

async function migrateV1ToV2(rdbStore: relationalStore.RdbStore) {
  await rdbStore.beginTransaction()
  try {
    await rdbStore.executeSql('ALTER TABLE recipe ADD COLUMN cover TEXT')
    await rdbStore.executeSql('UPDATE recipe SET cover = ? WHERE cover IS NULL', ['default.png'])
    await rdbStore.commit()
  } catch (error) {
    await rdbStore.rollBack()
    throw error
  }
}

事务的意义很直接:升级成功就是完整成功,失败就回到升级前。

第三层:迁移后要跑检查

迁移执行完,不代表数据一定对。至少要查字段是否存在、空值是否补齐、关键索引是否还能用。

async function verifyRecipeSchema(rdbStore: relationalStore.RdbStore) {
  const cursor = await rdbStore.querySql('SELECT COUNT(*) AS count FROM recipe WHERE cover IS NULL')
  cursor.goToFirstRow()
  const count = cursor.getLong(cursor.getColumnIndex('count'))
  cursor.close()
  if (count > 0) {
    throw new Error('recipe.cover still has empty rows')
  }
}

这个检查比“启动没报错”更可靠,因为它直接验证了迁移目标有没有达成。

两个用例怎么验

第一个用例是从 v1 升到 v2。准备一份没有 cover 字段的老库,升级后检查字段存在、旧数据能打开、默认封面已补齐。

第二个用例是迁移中断。故意让 UPDATE 抛错,升级后检查事务是否回滚,schemaVersion 不能被错误写成目标版本。

方案对比

方案 好处 风险
启动时直接 ALTER TABLE 写得快 半成品风险高,重复执行风险高
只维护版本号 能知道迁移进度 失败时仍可能留下脏数据
版本号 + 事务 + 迁移后检查 最稳 需要写迁移脚本和验证脚本

我会选第三种。RDB 升级不是把 SQL 跑完就行,关键是老用户数据升级后还能稳定使用。

最后怎么避免再出问题

以后每改一次表结构,我都会同时补三样东西:schema 版本号、迁移函数、迁移后检查。没有检查的数据库升级,发布前都不能算真正完成。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐