Android Room 数据库迁移与版本管理

举报
yd_290101916 发表于 2026/10/15 09:22:09 2026/10/15
【摘要】 Android Room 数据库迁移与版本管理本地数据库一旦发布,结构就变成了契约的一部分。升级时删除表、重建库或丢失用户数据,代价远高于开发阶段的任何便利。Room 的迁移机制要求开发者显式处理版本变化,这正是为了避免线上数据不可恢复。 一、数据库版本必须只增不减Room 通过版本号判断当前结构与代码模型是否一致。发布到线上的每个版本都应有稳定的版本号和迁移路径。@Database( ...

Android Room 数据库迁移与版本管理

本地数据库一旦发布,结构就变成了契约的一部分。升级时删除表、重建库或丢失用户数据,代价远高于开发阶段的任何便利。Room 的迁移机制要求开发者显式处理版本变化,这正是为了避免线上数据不可恢复。

一、数据库版本必须只增不减

Room 通过版本号判断当前结构与代码模型是否一致。发布到线上的每个版本都应有稳定的版本号和迁移路径。

@Database(
    entities = [OrderEntity::class],
    version = 3
)
abstract class AppDatabase : RoomDatabase()

降级安装、渠道包和测试包也要考虑版本关系。不要在开发过程中随意回退版本号,否则已安装用户可能无法正常打开数据库。

二、新增字段需要迁移

实体新增字段后,数据库版本应递增,并编写迁移脚本为已有数据补充默认值。

val MIGRATION_2_3 = object : Migration(2, 3) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL(
            "ALTER TABLE orders ADD COLUMN remark TEXT DEFAULT '' NOT NULL"
        )
    }
}

如果只是增加可空字段且无默认值要求,迁移也可以不做数据回填,但新代码读取时必须能正确处理旧数据。

三、建表语句必须与实体一致

迁移中的表结构必须与 Room 生成的期望结构一致,包括字段类型、非空约束、默认值、索引和主键。

CREATE TABLE IF NOT EXISTS orders (
  id TEXT PRIMARY KEY NOT NULL,
  amount INTEGER NOT NULL,
  created_at INTEGER NOT NULL,
  remark TEXT NOT NULL DEFAULT ''
)

手工迁移最容易出错的地方是默认值和索引。迁移完成后应通过 MigrationTestHelper 验证结构一致,而不是依赖线上反馈。

四、索引变更不能遗漏

删除或重命名字段后,相关索引也必须同步调整。Room 校验的是最终结构,任何索引差异都会导致打开数据库失败。

db.execSQL("DROP INDEX IF EXISTS index_orders_created_at")
db.execSQL("CREATE INDEX IF NOT EXISTS index_orders_created_at ON orders(created_at)")

批量重建索引会增加迁移耗时,应控制迁移时间,避免升级卡顿或触发系统超时。

五、使用 schema 导出管理版本

Room 支持将每个版本的数据库结构导出为 JSON 文件,作为迁移测试和代码评审的依据。

room.schemaLocation = $projectDir/schemas

schema 文件应纳入版本控制。没有 schema 的历史版本,迁移测试就无法准确编写,后续维护成本会显著上升。

六、迁移测试覆盖真实数据

迁移测试不只验证结构,也应验证数据保留。

@Test
fun migrate2To3KeepsOrders() {
    helper.createDatabase(DB_NAME, 2).apply {
        execSQL("INSERT INTO orders VALUES('1', 100, 1700000000000)")
        close()
    }

    val db = helper.runMigrationsAndValidate(DB_NAME, 3, true, MIGRATION_2_3)
    val count = db.query("SELECT COUNT(*) FROM orders").use { cursor ->
        cursor.moveToFirst()
        cursor.getInt(0)
    }

    assertEquals(1, count)
}

测试数据应包含旧版本可能出现的数据形态,而不是只插入一条最简单的记录。

七、重命名字段不能只改实体

Room 不会自动把旧字段迁移到新字段。字段重命名需要显式 SQL。

ALTER TABLE orders RENAME COLUMN title TO orderTitle

如果 SQLite 版本不支持字段重命名语法,需要按“创建新表、迁移数据、删除旧表、重命名”的流程处理。

八、跨版本迁移要逐步执行

用户可能长期不升级应用,再次打开时需要从很旧的版本迁移到最新版本。应逐级注册迁移,让 Room 自动串联。

Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
    .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
    .build()

每个迁移只处理相邻版本,避免为任意版本组合编写重复逻辑。

九、谨慎使用破坏性迁移

fallbackToDestructiveMigration 会在缺少迁移时删除所有数据。它只适合缓存数据库或调试构建,不应默认用于业务数据。

Room.databaseBuilder(context, AppDatabase::class.java, "cache.db")
    .fallbackToDestructiveMigration()
    .build()

本地草稿、订单和用户设置等数据必须提供真实迁移。数据是否可丢失,应作为数据库设计的第一决策。

十、数据库操作不要阻塞主线程

Room 的 DAO 挂起函数会在后台执行,但自定义迁移和手写查询仍要注意调用位置。

@Query("SELECT * FROM orders WHERE id = :id")
suspend fun getOrder(id: String): OrderEntity?

大量写入应使用事务分批处理,并观察执行时间。数据库升级可以在后台任务中完成,但应用不能在未完成前访问未就绪的数据。

总结

Room 版本管理的重点是可回溯和可验证。每个版本导出 schema,迁移脚本与实体结构完全一致,测试覆盖真实数据。新增字段、重命名和索引调整都要显式处理,跨版本迁移逐级串联。破坏性迁移只用于明确可丢弃的数据,业务数据库必须保证升级不丢数据。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。