Cherry Studio 跳过 V1 数据迁移:为何现在会先清除已迁移数据并以默认状态启动 V2
2026/9/19 17:39:20 网站建设 项目流程

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)起,行为变更如下:

  1. 迁移失败界面在"更多选项"(More options)下新增"直接使用 V2 / Use V2 without importing V1 data"入口;
  2. 选择该选项不再仅仅标记迁移完成,而是先清除迁移已写入新数据库的一切(包括已排程的 agent 任务),然后以默认数据启动 v2;
  3. V1 原始数据和迁移过程中已复制的文件仍然保留在磁盘上,不会被删除;
  4. 无论跳过发生在迁移开始前、版本不兼容的安装上,还是失败之后,现在都产生完全一致的干净起始状态

界面入口:三处共用同一个跳过对话框

在 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):
    1. 已迁移到新数据库的记录将被清除(如有),新版将以默认数据启动;
    2. 旧版原始数据不会被删除,仍保留在磁盘中,但对话、设置、知识库等内容不会出现在新版中;
    3. 迁移过程中已复制的文件可能仍占用磁盘空间,但不会出现在新版中;
    4. 仅当你确定要放弃本次自动迁移时继续。
  • 对话框可以随时取消(Cancel 按钮 / 关闭 / Esc),未确认前不会执行任何清理。

主进程执行链路:先清理导出目录,再清库并标记完成

Renderer 确认后调用migration:skip-migrationIPC,主进程的处理器位于 MigrationIpcHandler.ts,执行顺序如下:

  1. 并发保护:若已有迁移在运行(inFlightMigration非空),直接抛出Migration is already in progress.,绝不允许清理与正在写入的迁移交错执行;
  2. 清理导出临时目录cleanupExportDirectories()删除 Redux / Dexie / localStorage 三处登台的导出目录。注释明确说明:清理必须先于状态写入成功,否则下次启动会因状态已是completed而跳过迁移流程,导致这些大体积快照永远无法被清理;
  3. 执行migrationEngine.skipMigration()
  4. 关闭引擎并重启应用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)与"跳过"两条路径共享,注释明确要求二者不得漂移。

覆盖的表包括:pinai_usage_recordtag/entity_taguser_model/user_providermessage/topicpaintingassistant及其关联表、mcp_servermini_apppreferencenotetranslate_*knowledge_*groupprompt_*,以及 Agents 域全部表(agent_*agent_channel*agent_session*agent_workspace等)与文件域引用表(chat_message_file_reffile_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 配置写失败时数据库分毫不动persistdisk full后,skipMigration()拒绝,数据行与failed状态全部保持;
  • 状态写入失败时回滚所有清除:用 SQL 触发器在app_state插入前RAISE(ABORT)制造失败,验证事务中先执行的删除语句被一并回滚;
  • 与重试预清理共享同一份清理定义verifyAndClearNewTables()产生与跳过完全一致的效果。

用户需要做什么:无需任何操作

什么都不用做——跳过行为是自动生效的。确认对话框会事先明确告知:

  • 已迁移的记录将被清除;
  • V1 数据不会被删除;
  • 迁移将不再被提示。

并保留 10 秒倒计时后才可点击确认,且随时可取消。如果你希望保留数据,有两个替代选择:

  1. 重试(Retry):回到介绍页重新执行迁移——失败页的主操作按钮,重试前会通过verifyAndClearNewTables先清空上次残留,保证从干净库开始;
  2. 继续使用 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询