4. 迁移策略选型:全量迁移、增量迁移、混合策略的适用场景与风险权衡

从 Room 迁移到 DataStore,核心难点不是 API 差异,而是存量数据如何平滑过渡。选择错误的迁移策略可能导致数据丢失、UI 冻结、首次启动时间过长,甚至回滚困难。本章从原理、实现流程、风险三个维度拆解三种主流策略,并给出 Android 开发者的实战选型清单。

前置条件:在开始任何迁移前,必须先确定 Room 数据库的 schema 版本、预估数据总量(行数 × 平均字段数)以及目标 Preference Key 的命名规范。建议在项目 Wiki 中维护一份 Room-to-DataStore Key Mapping 文档。

4.1 全量迁移(Bulk Migration)

原理:在 App 启动后的一次性 Session 中,从 Room 查询全部记录,逐条或批量写入 DataStore。迁移完成后标记 migration_complete = true,后续启动直接跳过。

适用场景 核心指标 主要风险
数据总量 < 2000 条,单条字段 < 10 个 迁移耗时 < 2s,内存峰值 < 50MB UI 线程阻塞、电池突增、大表 OOM
用户量较小(MVP 阶段) 成功率 > 99% 迁移中途崩溃后无法断点续传
数据模型与 DataStore Key 一一映射 代码复杂度低 Key 冲突或类型不匹配
// 全量迁移典型实现(使用协程 + Dispatchers.IO)
suspend fun bulkMigrate(
    roomDao: UserSettingDao,
    dataStore: DataStore<Preferences>
) {
    val startTime = System.currentTimeMillis()
    val settings = roomDao.getAll() // 一次性加载全部

    dataStore.edit { prefs ->
        settings.forEach { setting ->
            prefs[stringPreferencesKey("setting_${setting.key}")] = setting.value
        }
        prefs[booleanPreferencesKey("migration_complete")] = true
        prefs[longPreferencesKey("migration_duration_ms")] =
            System.currentTimeMillis() - startTime
    }
}
风险红线:全量迁移期间 DataStore 的 edit 是独占操作。如果迁移循环超过 5s,系统会触发 ANR。务必在 Dispatchers.IO 中执行,且利用 awaitClose 或 catch 保证异常时回滚标记。

4.2 增量迁移(Incremental Migration)

原理:保留 Room 为旧数据源,仅在用户产生新行为时双写 DataStore + Room,逐步用新存储替代旧存储。最终通过开关阈值(如双写持续 7 天)完全切换到 DataStore。

适用场景 核心指标 主要风险
存量数据 > 10000 条,无法容忍一次性 I/O 双写延迟 < 10ms/次 数据不一致、回滚逻辑复杂
需要灰度迁移(按用户比例开放) 可监控双写差异率 < 0.1% A/B 测试与旧版本兼容冲突
业务字段频繁更新,全量迁移成本高 迁移期间无感 DataStore 与 Room 同时维护,存储翻倍
// 增量迁移:双写模式(Room + DataStore)
suspend fun incrementalWrite(
    roomDao: UserSettingDao,
    dataStore: DataStore<Preferences>,
    key: String,
    newValue: String
) {
    // 1. 先写 Room(原有事务)
    roomDao.update(UserSetting(key, newValue))

    // 2. 再写 DataStore(增量写入)
    dataStore.edit { prefs ->
        prefs[stringPreferencesKey("setting_$key")] = newValue
    }
}

// 后台差异校验协程
fun startDriftChecker(roomDao: UserSettingDao, dataStore: DataStore<Preferences>) {
    scope.launch(Dispatchers.IO) {
        while (isActive) {
            delay(30_000L) // 每 30s 抽样对比
            val roomSample = roomDao.getLast100Modified()
            val dsSample = dataStore.data.first()
            if (driftExceedsThreshold(roomSample, dsSample)) {
                triggerRepair()
            }
        }
    }
}
调试技巧:在增量迁移阶段,通过 RoomDatabase.setQueryCallback 记录所有写操作日志,与 DataStore 的 dataStore.data.first() 快照做行级对比,可快速定位漏写字段。

4.3 混合策略(Hybrid Strategy)

原理:第一次启动执行轻量全量(只迁移最核心的 200 条配置),同时开启增量双写兜底。后续版本再通过 Feature Flag 逐步扩大全量范围,直到 100% 覆盖。这是目前大型 App(如 Slack、Dropbox)推荐的迁移模式。

适用场景 核心指标 主要风险
数据量 2000 – 100000 条,且字段重要性分优先级 首次迁移 < 1s,全量覆盖周期 < 3 版本 状态机复杂、全量/增量边界模糊
团队有 Feature Flag 基础设施(如 LaunchDarkly) 可远程动态调整全量比例 多次迁移标记冲突
需要兼容 Android 6–15 多版本 迁移成功率 > 99.5% 旧版本 DataStore 版本不兼容
// 混合策略状态机核心逻辑
enum class MigrationPhase { NONE, FULL_BASELINE, INCREMENTAL, COMPLETE }

suspend fun hybridMigration(
    roomDao: UserSettingDao,
    dataStore: DataStore<Preferences>
) {
    val phase = dataStore.data.first()[enumPreferencesKey("phase")] ?: MigrationPhase.NONE
    when (phase) {
        MigrationPhase.NONE -> {
            // 全量基线:只迁移高频配置
            val baselineKeys = listOf("theme", "lang", "font_size", "notifications")
            val baselineData = roomDao.getByKeys(baselineKeys)
            dataStore.edit { prefs ->
                baselineData.forEach { prefs[stringPreferencesKey(it.key)] = it.value }
                prefs[enumPreferencesKey("phase")] = MigrationPhase.FULL_BASELINE
            }
        }
        MigrationPhase.FULL_BASELINE -> {
            // 增量双写 + 后台静默全量剩余数据
            enableDualWrite()
            backgroundFullRemaining(roomDao, dataStore)
        }
        MigrationPhase.INCREMENTAL -> {
            // 等待全量完成标记
        }
        MigrationPhase.COMPLETE -> { /* 只读 DataStore */ }
    }
}

4.4 三种策略对比与选型决策树

Room → DataStore 迁移策略选型决策树 开始迁移评估 数据总量是否 < 2000 条? 且字段数 < 10 是 ✅ 全量迁移 一次性加载 + 批量写入 风险:OOM / ANR 否 ❌ 可划分优先级? 核心字段 ≤ 10 个 是 混合策略 基线全量 + 增量双写 风险:状态机复杂 否 增量迁移 双写 + 后台差异校验 风险:数据不一致 实施监控 + 回滚预案 成功率 / 耗时 / 数据差异告警 迁移完成 💡 说明:决策阈值可根据 具体机型(低端机 < 500 条) 和用户网络状态动态调整

4.5 风险权衡全景表

风险维度 全量迁移 增量迁移 混合策略
数据丢失概率 低(一次性事务) 中(双写窗口期) 低(基线保障)
ANR 风险 高(大表) 低(单次 1–2 条) 中(基线批量)
代码维护成本 低 中(需双写 + 校验) 高(状态机 + Feature Flag)
回滚复杂度 低(清除标记即可) 高(需还原双写) 中(按阶段回退)
灰度能力 无 强(按用户比例) 强(远程配置)
存储占用 低(迁移后可删 Room) 高(双库共存) 中(过渡期双库)

4.6 调试与常见错误

  • 错误 1:全量迁移未设置超时保护
    现象:低端机型 ANR,日志显示 edit 占用 > 10s。
    修复:每次 edit 内写入不超过 500 条,分批提交。
  • 错误 2:增量迁移 Key 冲突
    现象:Room 字段值被 DataStore 旧值覆盖。
    修复:双写时以 Room 为主数据源,DataStore 仅做副本,写前先比较 lastModified。
  • 错误 3:混合策略中间态丢失
    现象:用户从 FULL_BASELINE 状态启动,未进入 INCREMENTAL 导致后续数据不生效。
    修复:使用 enumPreferencesKey 持久化阶段,并在 onStart 添加补偿检查。

4.7 性能优化清单

  1. 全量分批:每批 200 条写入 DataStore,中间添加 delay(5) 避免绑定 CPU。
  2. 增量批量:利用 edit 的批量 putAll,减少序列化次数。
  3. 混合策略缓存:基线数据从 Room 读取后,在内存中按优先级排序,高优字段先写。
  4. 降级预案:当迁移失败次数 > 3 次时,自动降级为只读 Room,次日重试。
  5. 监控指标:上报 migration_type、total_records、duration_ms、error_code 到 FireBase / 自有埋点。

4.8 本章小结