1. 数据库版本概念与迁移场景

1.1 版本号理解

Room 使用 整型版本号 标识数据库 Schema。每次在 @Database 注解中声明的 version 值改变,Room 自动触发迁移流程。版本号必须严格递增,低版本代表旧 Schema,高版本代表新 Schema。

概念说明
当前版本用户设备上已安装数据库的版本号
目标版本新 APP 代码中 @Database(version=…) 声明的版本号
迁移流程Room 从当前版本依次执行到目标版本的迁移步骤

典型声明示例:

@Database(
    entities = [User::class, Task::class],
    version = 3,
    exportSchema = true
)
abstract class AppDatabase : RoomDatabase() { … }

exportSchema = true 会生成 JSON Schema 文件,便于对比版本差异。

1.2 迁移触发条件

当 APP 启动时,Room 检测到 当前数据库版本 < 目标版本 且存在合法 Migration 对象时,自动执行迁移。若不存在对应 Migration,则 抛出 IllegalStateException 并强制用户卸载重装(默认 FallbackToDestructiveMigration 除外)。

  • 版本不匹配:设备上数据库 version ≠ 代码中 version。
  • Migration 缺失:未注册从旧版到新版的 Migration 实例。
  • 降级禁止:Room 不支持降级,必须卸载重装或使用 destructive 回退。

关键避坑: 若多次修改 Schema 但未定义对应 Migration,用户升级时将直接崩溃。始终在 addMigrations() 中注册所有历史迁移路径。

1.3 常见业务场景

场景变化内容迁移策略
新增表添加新实体类CREATE TABLE …
新增列字段非空且带默认值ALTER TABLE ADD COLUMN … DEFAULT …
修改列类型改变字段数据类型创建临时表 + 数据复制 + 删除旧表 + 重命名
删除表/列移除实体或字段直接 DROP TABLE 或使用 DROP COLUMN(SQLite 3.35+)
重命名表/列名称变更ALTER TABLE RENAME TO / COLUMN RENAME TO
添加索引提升查询性能CREATE INDEX …

1.4 迁移流程全景图

下图展示了从版本 1 到版本 3 的迁移触发与执行路径,包含 版本校验、迁移查找、逐步执行 三个核心阶段。

Room 数据库版本迁移流程图 Room 数据库版本迁移流程图 APP 启动 检查当前版本 当前版本 != 目标版本? 否 正常使用 是 查找 Migration Migration 存在? 是 执行迁移 否 抛出异常 更新版本号并完成 迁移完成 注意事项 • 迁移必须按版本顺序执行 • 降级需要 destructive 回退

1.5 代码与 API 要点

在 Room 中注册迁移的典型做法如下:

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE User ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
    }
}

val MIGRATION_2_3 = object : Migration(2, 3) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("CREATE TABLE Task (id INTEGER PRIMARY KEY, name TEXT)")
    }
}

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

1.6 调试与验证

开启 Room 调试日志可实时查看迁移 SQL:

adb shell setprop log.tag.Room VERBOSE
# 或使用 RoomDatabase.Builder 的 enableMultiInstanceInvalidation() 配合日志

建议在单元测试中编写 MigrationTestHelper 验证迁移正确性:

@RunWith(AndroidJUnit4::class)
class MigrationTest {
    private val helper = MigrationTestHelper(
        InstrumentationRegistry.getInstrumentation(),
        AppDatabase::class.java.canonicalName,
        FrameworkSQLiteOpenHelperFactory()
    )

    @Test
    fun migrate1to2() {
        val db = helper.createDatabase("testdb", 1)
        db.execSQL("INSERT INTO User (id, name) VALUES (1, 'test')")
        val migratedDb = helper.runMigrationsAndValidate("testdb", 2, true, MIGRATION_1_2)
        // 校验新列 age 存在且默认为0
    }
}

1.7 常见错误与稳定性优化

错误原因解决方案
IllegalStateException: Migration not found未注册所有迁移路径添加缺失的 Migration 或启用 fallbackToDestructiveMigration()
SQLiteConstraintException 迁移中新增列未设置默认值,已有数据违反非空约束ALTER TABLE 时指定 DEFAULT 值,或先增加 nullable 列再填充
迁移耗时过长 ANR大数据量复制或复杂数据转换将迁移拆分为多个事务,或使用 beginTransaction / setTransactionSuccessful
Schema 校验失败exportSchema 生成的 JSON 与实际迁移结果不一致删除 JSON 缓存并重新构建,确保迁移 SQL 严格匹配目标 Schema

性能优化建议:

  • 批量 DDL 语句放在同一个 migrate() 方法中,减少事务开销。
  • 对于大型表的重构,使用 INSERT INTO … SELECT … 一次性迁移数据,避免逐行操作。
  • 在 @Database 中设置 exportSchema = true 并配合 CI 检查 Schema 差异,防止未定义迁移的版本升级。