ITPub博客

首页 > 移动开发 > ANDROID > Android Room 数据库踩坑:手动升级和自动升级一起使用导致数据库报错

Android Room 数据库踩坑:手动升级和自动升级一起使用导致数据库报错

原创 ANDROID 作者:山有木xi 时间:2024-12-24 13:23:35 0 删除 编辑

本篇文章已同步更新到我的公众号“柿蒂”中,喜欢本篇文章的小伙伴可以关注,感谢大家

https://mp.weixin.qq.com/s/N7VDL-Y8nzDHVriwpwT0RQ


在最近的项目开发中,我有个产品需求,需要修改到原来的数据库,增加一个字段,对于数据库操作原本以为很简单,但是没想到踩了一个巨大的坑,今天有空对这个问题进行分析,在这过程中学习到了很多,成长的代价总是惨痛的。

一、问题背景

在项目迭代中,我们需要对数据库进行升级,为一张表新增一个字段:

    @ColumnInfo(defaultValue = "1")  
    var status: Int = 1,

起初认为这只是一次普通的字段更新,但问题却远比想象中复杂。我们在 版本 10 新增了表,在 版本 11 为该表新增字段。直接从线上版本(10)升级到最新版本(11)一切正常,但从更老的版本(如 9)升级到版本 11 时却爆出了以下异常:

android.database.sqlite.SQLiteException: duplicate column name: newColumn 
(Sqlite code 1 SQLITE_ERROR): , while compiling: ALTER TABLE target_table 
ADD COLUMN newColumn INTEGER NOT NULL DEFAULT 1, 
(OS error - 2: No such file or directory)

这个问题背后的原因正是  Room 的自动迁移与手动迁移同时启用导致冲突。

二、Room 的迁移执行顺序

Room 数据库在升级时,遵循以下迁移执行顺序:

  1. 优先执行自动迁移:
    Room 会根据  autoMigrations 配置自动完成表结构变更。

  2. 随后执行手动迁移:
    如果同一区间的版本还配置了手动迁移,Room 不会跳过,而是继续执行手动迁移逻辑。

我们的问题代码如下:

@Database(
    version = 11,
    autoMigrations = [
        AutoMigration(from = 9, to = 10),  // 自动迁移:9 → 10
        AutoMigration(from = 10, to = 11) // 自动迁移:10 → 11
    ]
)
abstract class AppDataBase : RoomDatabase() {
    companion object {
        val MIGRATION_10_11 = object : Migration(10, 11) {  // 手动迁移:10 → 11
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE table_name ADD COLUMN new_column_name INTEGER NOT NULL DEFAULT 1")
            }
        }
    }
}

在这种情况下,从版本 9 → 11 升级时,Room 会依次执行以下步骤:

  1. 自动迁移  9 → 10。

  2. 自动迁移  10 → 11。

  3. 手动迁移  10 → 11。

当自动迁移和手动迁移针对相同字段( new_column_name)操作时,就会导致冲突。

三、问题复现分析

1. 自动迁移的操作

自动迁移会通过表重建完成变更,具体过程如下:

-- 创建新表,包含新增字段
CREATE TABLE IF NOT EXISTS `_new_table_name` (
    id INTEGER PRIMARY KEY,
    column_1 TEXT,
    new_column_name INTEGER NOT NULL DEFAULT 1
);
-- 将旧表数据迁移到新表
INSERT INTO `_new_table_name` (id, column_1) 
SELECT id, column_1 FROM `table_name`;
-- 删除旧表
DROP TABLE `table_name`;
-- 重命名新表为旧表
ALTER TABLE `_new_table_name` RENAME TO `table_name`;

2. 手动迁移的操作

自动迁移完成后,Room 会继续执行手动迁移:

ALTER TABLE table_name ADD COLUMN new_column_name INTEGER NOT NULL DEFAULT 1;

此时,由于自动迁移已包含  new_column_name 字段,手动迁移尝试再次添加该字段,导致以下错误:

android.database.sqlite.SQLiteException: duplicate column name: new_column_name


四、问题原因

1.  重复操作

自动迁移和手动迁移在同一区间内同时操作了相同字段  new_column_name:

-- 自动迁移已包含该字段
CREATE TABLE `_new_table_name` (
    ...
    new_column_name INTEGER NOT NULL DEFAULT 1
);
-- 手动迁移再次尝试添加同字段
ALTER TABLE table_name 
ADD COLUMN new_column_name INTEGER NOT NULL DEFAULT 1;  -- 冲突

2.  状态不一致

手动迁移的逻辑是基于旧的表结构设计的,而自动迁移在执行完成后已更新表结构,导致手动迁移的操作无效或冲突。

五、Room 的设计缺陷

缺乏冲突检测机制

Room 没有提供机制来防止以下情况:

  1. 自动迁移与手动迁移的逻辑冲突:两个迁移同时作用于同一个版本区间。

  2. 自动迁移结果与手动迁移前提条件不一致:手动迁移未意识到自动迁移已更新表结构。

自动迁移生成代码示例

@Generated("androidx.room.RoomProcessor")
class AutoMigration_10_11_Impl : Migration(10, 11) {
    override fun migrate(database: SupportSQLiteDatabase) {
        // 表重建的完整逻辑
    }
}

手动迁移示例:

val MIGRATION_10_11 = object : Migration(10, 11) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE table_name ADD COLUMN new_column_name...")
    }
}

两者在迁移过程中没有明确的优先级或冲突解决机制。

六、总结

自动迁移与手动迁移的混用容易引发操作冲突,其核心问题在于迁移逻辑的重叠与 Room 的冲突检测不足。在实际开发中,我们应根据以下建议优化迁移设计:

  1. 优先选择适合当前变更的迁移方式。

  2. 确保自动迁移与手动迁移的区间不重叠。

  3. 在发布前充分测试所有可能的升级路径。

通过合理设计迁移逻辑,可以避免问题复现,确保数据库升级的安全性与一致性。



来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69917874/viewspace-3058812/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
Oracle OCA(Java),IBM高级工程师认证,中国软件行业人才(高级c语言),高级Android工程师,对数据库并发与性能调优也有一定了解

注册时间:2019-04-25

  • 博文量
    147
  • 访问量
    390756