SuperAPI AI 工具集

room-migrations-at-scale

数据与分析

查看项目

更新于 10-17 16:16

技能介绍

用途

该技能面向已经积累大量 schema 版本的 Room 数据库项目,讲解如何在一款长期存续的应用中持续保持数据库可升级。它的核心论点是:迁移构成一张有向图,而不是一条阶梯。Room 会从磁盘上的版本走向代码中的版本,而它只能走你已经声明过的边。长期存在的应用,其用户几乎分布在曾经发布过的每一个版本上,因此起点是整段历史。

主要能力

文档区分两类迁移边,它们覆盖不同的工作:

  • 生成式(autoMigrations = [AutoMigration(from = 23, to = 24)]):在构建期通过对比两份导出的 schema 文件生成。只能表达 schema 差异所能表达的内容,即新增表与列,并可通过 spec 类处理重命名与删除。
  • 手写式(.addMigrations(object : Migration(5, 6) { … })):任何需要移动数据的场景。

使用方式与关键陷阱

迁移遍历器会逐步串联单步迁移:可达性要求每一条相邻边都存在,fan-in(多条边直接指向同一新版本)则是其上的优化。一串从 N 到 N+1 的连续边就能把每个旧版本带到最新版本;指向每个新目标的额外 fan-in 边可以缩短常见升级路径,每条额外边只多一个生成类、运行时无开销。

覆盖情况可以通过列出所有声明的 from 来检查:确认每个版本要么有 N→N+1 边,要么有跨越缺口的直连边。链条只有在每个中间边都存在时才可走通,跳过一条边会让它之前的所有版本都变得不可达;当确实跳过某一步时,要用一条跨越缺口的直连边说明。

生成式迁移不能移动数据,而且失败不会表现为构建错误。schema 差异看到“列被删除、表被新增”时,会直接丢弃旧内容。有两类形态始终需要手写步骤:把列重塑为行(如把 JSON 数组列拆成 join 表行),以及用不同主键重建表。判断依据是:迁移体在任何 DDL 之前先执行了 SELECT,就无法生成。

手写迁移中的插值值是被拼接而非绑定的。示例中 connection.execSQL 用字符串拼接构造 INSERT 语句,而这些值来自用户数据库,含引号的值会提前终止语句,使迁移中途失败。应改为预编译语句并绑定参数;若保留拼接,则要转义文本值,并能说明数据为何不可能包含该分隔符。

触发器、视图和 pragma 不属于 Room 迁移的 schema 范围:全新安装时没有任何东西创建它们,表重建后也没有东西重建它们。应把它们放进 on-open 回调,并写成重复执行无害的形式(使用 CREATE TRIGGER IF NOT EXISTS)。onOpen 在每次打开时、任何迁移执行之后触发,正是全新安装与升级之后都能保持正确的位置。

在多平台模块中,迁移列表注册在平台的 actual 里,因此只存在于注册它的平台上。示例中 addMigrations(...) 与 addCallback(...) 只写在 Android builder 中,其他平台没有触发器也没有手写步骤,且不会给出警告。应把共享注册提升到每个 actual 都会调用的公共函数中,让 actual 只保留文件路径。

应把每个导出的 schema 文件纳入版本控制:生成式迁移通过对比 from 与 to 的文件产生。删除 17.json 后构建仍然成功,直到有人声明一条从 17 出发的边,而差异无文件可读。schema 目录只需配置一次(room { schemaDirectory("$projectDir/schemas") }),并应像源码一样被审阅。

删除规范是一种声明,而不是删除本身。@DeleteTable / @DeleteColumn 告诉生成器发生了什么变化,以便计算差异。当意图是“表已消失”时,还应显式声明,例如在 onPostMigrate 中执行 DROP TABLE IF EXISTS。

验证升级路径应从旧数据库文件开始,而不是从全新数据库开始:保留最近几个版本的真实数据库副本,用当前代码打开它们。全新安装不会走任何迁移,因此“在我机器上能跑”在这里什么也证明不了。

验证方法

文档给出三段可从仓库根目录运行的验证:

  1. 用 Python 脚本从 Kotlin 源码中提取所有 AutoMigration 与 Migration 边,并结合 version 计算可达性。通过条件为输出 unreachable none;列表中的版本是 Room 无法打开的数据库,且是静默失败,直到有人触发它。
  2. 用 grep 检查 addMigrations / addCallback 的注册位置。通过条件为所有命中都在 MusicDatabase.android.kt 内,jvm 与 ios 的 actual 中没有。
  3. 在草稿库(绝不能用真实应用数据库)上连续两次执行同一触发器创建语句,再统计触发器数量。通过条件为两次都打印 ok,最终计数为 1,即两次创建后只留下一个触发器。

适用场景

适用于 schema 版本数量多、用户版本分布广、需要长期维护升级路径的 Room 数据库项目,尤其是采用多平台模块、包含数据搬移或表重建迁移、并依赖触发器或视图的场景。

限制

文档所述内容集中于 Room 迁移的图结构与验证方式;具体版本数量、项目规模等未提供通用数值,需按实际项目判断。生成式迁移的表达能力受限于 schema 差异,涉及数据搬移的部分必须手写。多平台下未注册迁移的平台不会有对应触发器与手写迁移步骤。