1. Room 数据库版本机制与迁移基础
本章是《Room 数据库迁移与版本管理实操》的基石。我们将彻底拆解 Room 的版本控制哲学:为什么需要版本号?fallbackToDestructiveMigration 究竟是“逃生舱”还是“数据毁灭者”?迁移失败时 Room 又会如何反应?掌握这些底层机制,才能安全、稳健地演进你的本地数据库架构。
核心要点:版本号是数据库 Schema 的“快照哈希”;迁移失败默认抛异常而非静默降级;
fallbackToDestructiveMigration 是最后手段,慎用。
1.1 版本号管理:数据库 Schema 的身份证
Room 通过 @Database(version = ...) 声明数据库版本号。每次实体(Entity)结构、字段类型、索引、外键等 Schema 变化,必须递增版本号。Room 在编译期生成 _Impl 类,运行时对比当前已打开的数据库版本与声明版本:
- 版本一致:直接正常打开。
- 版本不一致:触发迁移逻辑。
| 元素 | 说明 |
|---|---|
| @Database(version = 1) | 初始版本,通常对应首个 Release |
| version = 2 | 首次 Schema 变更,必须提供 Migration(1, 2) |
| version = N | 每次变更递增 1,建议使用当前 Release 编号或独立 DB_VERSION 常量 |
| fallbackToDestructiveMigration() | 未匹配到 Migration 时的降级开关,默认关闭 |
常见错误:只递增版本号却忘记添加 Migration 实现,且未开启 fallback。后果——
IllegalStateException 应用直接崩溃。
1.2 fallbackToDestructiveMigration:毁灭性回退的代价
当 Room 找不到从当前版本到目标版本的迁移路径时,默认行为是抛出 IllegalStateException 并终止数据库打开。若你显式调用 fallbackToDestructiveMigration(),Room 会将旧数据库文件清空,然后按最新 Schema 重建一个新数据库。数据全部丢失。
// 开启毁灭性回退的典型配置
@Database(entities = [User::class], version = 3)
abstract class AppDatabase : RoomDatabase() {
companion object {
fun getInstance(context: Context): AppDatabase {
return Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"myapp.db"
)
.fallbackToDestructiveMigration() // 不推荐用于生产!
.build()
}
}
}
1.3 迁移失败时的默认行为:严格模式
Room 默认采用严格迁移验证。迁移过程包括两个阶段:
- 准备阶段:验证 Migration 对象是否存在,版本号是否合法。
- 执行阶段:运行 Migration.migrate(),提交事务。
若 migrate 中抛出 SQL 异常(如 SQLiteConstraintException 约束冲突、表已存在等),Room 会回滚整个迁移事务并向上层抛出异常,数据库保持迁移前的旧版本。绝不会部分应用迁移。
调试技巧:在
migrate() 内使用 Log.d 打印当前 SQL,或通过 .openHelperFactory(SupportFactory()) 拦截 SQL 执行日志。
1.4 版本机制与迁移关系流程图
下图清晰展示了 Room 启动时版本比对以及迁移调用的完整路径:
1.5 常见错误与稳定性优化
- 错误:版本号回退——从 version=3 降到 version=2 会导致 Room 抛
IllegalStateException,不允许降级。 - 错误:并发迁移——多线程同时调用数据库构建,可能多次执行迁移。应使用单例模式 + 同步锁。
- 优化:显式版本常量——定义
object DBVersion { const val V1 = 1; const val V2 = 2 },避免硬编码。 - 优化:预检 Schema 差异——使用
@Database(exportSchema = true)导出 JSON Schema,通过 diff 工具检查预期与实际变更是否一致。
生产建议:始终关闭
fallbackToDestructiveMigration()。使用 .addMigrations() 精确描述每次 Schema 变更。若开发阶段希望快速重建,可使用 .fallbackToDestructiveMigrationOnDowngrade() 控制降级行为。