Android Room 数据库迁移与版本管理
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,迁移脚本与实体结构完全一致,测试覆盖真实数据。新增字段、重命名和索引调整都要显式处理,跨版本迁移逐级串联。破坏性迁移只用于明确可丢弃的数据,业务数据库必须保证升级不丢数据。
- 点赞
- 收藏
- 关注作者
评论(0)