Cherry Studio 跳过 V1 数据迁移:为何现在会先清除已迁移数据并以默认状态启动 V2
【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio
本文面向 Cherry Studio 用户与维护者,讲解 v2 数据迁移的"跳过"(Skip)行为变更:从 2026-07-29 起,在迁移失败界面选择"不导入 V1 数据直接使用 V2",程序会先清除本次迁移已写入新数据库的全部数据(含已排程的 agent 任务),再以默认数据启动 v2,而 V1 原始数据与迁移过程中已复制的文件仍完整保留在磁盘上。读完本文,你将理解这一行为背后的分步提交机制、跳过操作的完整执行链路与原子性保证,以及界面上的 10 秒确认倒计时和各项操作的含义。
变更背景:迁移是分步提交的,失败会留下半成品数据
Cherry Studio 从 v1 升级到 v2 时,数据迁移不是"一个巨大的事务",而是按迁移器(Migrator)逐个独立提交。在 MigrationEngine.run() 中可以看到,每个迁移器依次经历 prepare(含 dry-run 校验)→ execute(各自管理自己的事务)→ validate 三个阶段,任何一个迁移器失败都会让整次运行进入error阶段并标记失败状态,但在此之前已经成功提交的迁移器数据会留在新数据库里。
这正是本变更要解决的痛点:一次跑到一半失败的迁移,会让新数据库中混有"已导入的部分数据 + 默认数据",用户根本无法分辨哪些行来自旧版、哪些是默认生成的。旧的跳过逻辑只是把迁移状态标记为完成,随后 v2 就启动在这堆"部分导入行"之上,产生一个不干不净的起始状态。
变更内容:跳过 = 清空迁移产物 + 以默认数据启动
自 2026-07-29(PR #17593)起,行为变更如下:
- 迁移失败界面在"更多选项"(More options)下新增"直接使用 V2 / Use V2 without importing V1 data"入口;
- 选择该选项不再仅仅标记迁移完成,而是先清除迁移已写入新数据库的一切(包括已排程的 agent 任务),然后以默认数据启动 v2;
- V1 原始数据和迁移过程中已复制的文件仍然保留在磁盘上,不会被删除;
- 无论跳过发生在迁移开始前、版本不兼容的安装上,还是失败之后,现在都产生完全一致的干净起始状态。
界面入口:三处共用同一个跳过对话框
在 MigrationApp.tsx 中,SkipMigrationDialog被三处复用:
| 场景 | 入口位置 | 触发方式 |
|---|---|---|
| 迁移开始前(介绍页) | 右上角 More options 图标 | 打开更多选项后选择"直接使用 V2" |
| 迁移失败后 | 错误页的"更多选项"(显示标签的按钮) | 选择"直接使用 V2" |
| 版本不兼容时 | version_incompatible页面 | 点击"忽略迁移 / Ignore migration"破坏性按钮 |
错误页面的 More options 共提供三个常规选项,顺序与 README.md 描述一致:先保存诊断信息(Save troubleshooting information)→直接使用 V2(不导入 V1 数据)→继续使用 V1(Continue using V1,打开 V1 下载页对话框)。每个选项都会等前一个对话框的关闭动画结束后再打开后续对话框,避免遮罩重叠与焦点错乱。
确认对话框:10 秒倒计时的破坏性操作
SkipMigrationDialog.tsx 实现了专门的破坏性确认对话框(COUNTDOWN_SECONDS = 10):
- 确认按钮初始为禁用态,经过10 秒倒计时后才变为可点击;
- 按钮文案随倒计时变化:
I understand the risk, skip and restart ({{seconds}}s),倒计时结束后为I understand the risk, skip and restart; - 对话框内以红色 Alert 标明"高危操作"(High-risk action),并列出四条要点(中英文文案见 locales.ts 与 locales.ts):
- 已迁移到新数据库的记录将被清除(如有),新版将以默认数据启动;
- 旧版原始数据不会被删除,仍保留在磁盘中,但对话、设置、知识库等内容不会出现在新版中;
- 迁移过程中已复制的文件可能仍占用磁盘空间,但不会出现在新版中;
- 仅当你确定要放弃本次自动迁移时继续。
- 对话框可以随时取消(Cancel 按钮 / 关闭 / Esc),未确认前不会执行任何清理。
主进程执行链路:先清理导出目录,再清库并标记完成
Renderer 确认后调用migration:skip-migrationIPC,主进程的处理器位于 MigrationIpcHandler.ts,执行顺序如下:
- 并发保护:若已有迁移在运行(
inFlightMigration非空),直接抛出Migration is already in progress.,绝不允许清理与正在写入的迁移交错执行; - 清理导出临时目录:
cleanupExportDirectories()删除 Redux / Dexie / localStorage 三处登台的导出目录。注释明确说明:清理必须先于状态写入成功,否则下次启动会因状态已是completed而跳过迁移流程,导致这些大体积快照永远无法被清理; - 执行
migrationEngine.skipMigration(); - 关闭引擎并重启应用(
migrationWindowManager.restartApp())。
底层实现:skipMigration 的两阶段原子操作
核心逻辑在 MigrationEngine.skipMigration(),分两个阶段:
阶段一:先恢复被迁移改动的 Boot 配置(DB 之外)
bootConfigService.set('app.disable_hardware_acceleration', DefaultBootConfig['app.disable_hardware_acceleration']) bootConfigService.persist()源码注释解释了顺序的必要性:先写 boot 配置,再动数据库事务。如果 boot 写入失败,数据库未受任何影响、状态保持原样,用户可以重试或再次跳过;反过来若先落库,就可能出现"状态已 completed 而迁移来的硬件加速配置永远保留"的永久性不一致。被恢复的只有app.disable_hardware_acceleration这一个 v1 派生键(见BootConfigMappings),而app.user_data_path标识着迁移入口时固定的数据目录,必须保留。
阶段二:同一事务内清库 + 写入 completed 状态
const db = this.getDb() db.transaction((tx) => { this.clearMigrationData(tx) this.upsertMigrationStatus(tx, { status: 'completed', migratedFromV1: false, completedAt: Date.now(), version: '2.0.0', error: null }) }) this.migratedFromV1 = false清库与状态写入在同一个数据库事务中完成,二者要么全部成功、要么全部回滚。migratedFromV1: false会被needsMigration()与registerMigrationOriginReader读取,确保后续启动把该配置档案识别为"非 v1 迁移而来"。
清理范围:MIGRATION_TARGET_TABLES 是唯一事实来源
clearMigrationData(tx)(MigrationEngine.ts)逐表执行tx.delete(table).run(),清理范围由常量 MIGRATION_TARGET_TABLES 统一定义——该常量同时被"重试前的预清理"(verifyAndClearNewTables)与"跳过"两条路径共享,注释明确要求二者不得漂移。
覆盖的表包括:pin、ai_usage_record、tag/entity_tag、user_model/user_provider、message/topic、painting、assistant及其关联表、mcp_server、mini_app、preference、note、translate_*、knowledge_*、group、prompt_*,以及 Agents 域全部表(agent_*、agent_channel*、agent_session*、agent_workspace等)与文件域引用表(chat_message_file_ref、file_entry等)。顺序严格按"子表先于父表"排列以规避外键引用问题。
需要特别说明的例外:job_schedule是 v2 任务系统共享的表,清理时只删除type = 'agent.task'的迁移产物行,绝不触碰其他类型调度:
tx.delete(jobScheduleTable).where(eq(jobScheduleTable.type, 'agent.task')).run()另外,agent_task/agent_task_run_log已废弃并迁移至 JobManager,不在清理清单内。
测试如何验证这一行为
MigrationEngine.skip.test.ts 用真实数据库覆盖了跳过路径的完整语义,可作为理解实现的活文档:
- 清除迁移行与 agent.task 调度、保留其他调度:预置一行
preference和两条job_schedule(一条agent.task、一条other.job),执行skipMigration()后preference为空、job_schedule只剩other.job,状态为completed / migratedFromV1: false / error: null; - 恢复硬件加速默认值、不碰 user_data_path:断言
bootConfigService.set只收到app.disable_hardware_acceleration = false,且调用键列表不含app.user_data_path; - boot 配置写失败时数据库分毫不动:
persist抛disk full后,skipMigration()拒绝,数据行与failed状态全部保持; - 状态写入失败时回滚所有清除:用 SQL 触发器在
app_state插入前RAISE(ABORT)制造失败,验证事务中先执行的删除语句被一并回滚; - 与重试预清理共享同一份清理定义:
verifyAndClearNewTables()产生与跳过完全一致的效果。
用户需要做什么:无需任何操作
什么都不用做——跳过行为是自动生效的。确认对话框会事先明确告知:
- 已迁移的记录将被清除;
- V1 数据不会被删除;
- 迁移将不再被提示。
并保留 10 秒倒计时后才可点击确认,且随时可取消。如果你希望保留数据,有两个替代选择:
- 重试(Retry):回到介绍页重新执行迁移——失败页的主操作按钮,重试前会通过
verifyAndClearNewTables先清空上次残留,保证从干净库开始; - 继续使用 V1(Continue using V1):在 More options 中选择,打开
V1DownloadDialog,点击下载后由主进程MigrationIpcHandler根据向导当前语言解析地区化 V1 下载页(中文环境走cherryai.com.cn/download/v1,其余走cherryai.com/download/v1,见 MigrationIpcHandler.ts)。
需要留意的是:在部分失败后选择跳过,你将看不到那些恰好在报错前被导入的少量助手、设置或会话——这正是本变更的预期行为,用"可预期的干净初始状态"换取"无法分辨的半成品数据"。
发布管理者注意事项(Notes for Release Manager)
- 跳过操作永远不删除V1 数据目录,也不删除迁移期间已复制的文件——这些文件只是在新数据库中不再有任何引用,会继续占用磁盘空间。建议与本变更的发布说明配套,向用户解释如何回收这部分磁盘空间(例如手动清理对应目录);
- 迁移的
completed状态一旦写入,后续启动将不再进入迁移流程,因此清理导出临时目录等收尾动作必须在状态写入前完成,这一点已在 IPC 处理器中强制保证; - 跳过后的启动依赖默认数据填充:状态标记为
completed后,v2 的种子数据(seeders)会在启动时重新填充默认配置,因此不必担心清库后应用无数据可用。
小结
"跳过迁移"从"形式上结束迁移"升级为"真正回到干净的默认状态",其背后是三重保障:以MIGRATION_TARGET_TABLES为唯一事实来源的确定性清库、boot 配置恢复与数据库事务的两阶段顺序、以及"清理必须先于状态写入"的收尾纪律。对用户而言,规则变得简单可预期:选择跳过 = 放弃本次迁移结果但保留 V1 原件;选择重试 = 从头再来;选择继续使用 V1 = 回到旧版。
【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考