# 80 Admin:实现 SQLite 到 MySQL 8 单向迁移与核对 - 类型:需求 - 父级大工单:#76 - 所属 MVP / 版本:#77 - 状态:已完成 - 日期:2026-08-10 - Gitea 工单:http://ilaer.eicp.net:8418/chengma/cmautobuy/issues/80 ## 背景与目标 历史业务数据保存在 SQLite `admin.db`,生产 Admin 改用 MySQL 8 前,必须完整迁移并证明人工关联、规格映射、账号、任务、客户端归属和同步游标没有遗漏。 ## 最终方案 - 命令必须显式选择 `--dry-run`、`--execute` 或 `--verify-only`,避免误执行。 - SQLite 使用 `mode=ro` 和 `query_only` 双重只读,并检查 schema v8、`quick_check` 与外键。 - MySQL 目标必须具有当前 schema 且 15 张业务表全空;非空目标直接拒绝。 - 按外键依赖顺序在一个 MySQL 事务内复制全部业务列,保留主键、时间、JSON 和哈希原文。 - 导入后比较每张表的数量与全部迁移列的确定性 SHA-256 摘要,并检查关键孤儿关系和当前归属唯一性。 - 对外错误只包含阶段和表名,不透出可能含业务主键的数据库原始错误。 与建单方案一致。额外增加全列摘要核对,比仅比较行数更能发现字段漏迁或内容变化。 ## 改了哪些 - `admin/cmd/migrate-sqlite-to-mysql/main.go`:一次性迁移命令和三种显式模式。 - `admin/repository/sqlite_to_mysql.go`:只读检查、空库门禁、事务复制和核对。 - `admin/repository/sqlite_to_mysql_test.go`:真实 MySQL fixture、失败回滚和重复保护测试。 - `docs/admin/09-sqlite迁移到mysql.md`:演练、执行、核对、失败处理和切换门禁。 - `docs/README.md`、`docs/admin/00-getting-started.md`:补充迁移文档入口。 ## 验收结果 | 验收标准 | 结果 | |---|---| | 只读打开 SQLite,失败不修改源文件 | 通过 | | 非空 MySQL 目标默认拒绝 | 通过 | | v8 全表迁移后数量与全列摘要一致 | 通过 | | 关键业务关系、归属、任务、幂等与游标一致 | 通过 | | 重复执行不会重复导入 | 通过 | | 输出不包含凭据、Cookie、Session 或业务明细 | 通过 | | 提供可复制的演练、核对和失败处理命令 | 通过 | ## 测试 - 执行的命令:真实 MySQL fixture 定向测试;对当前 `admin/data/admin.db` 依次执行 `--dry-run`、`--execute` 和 `--verify-only`;`GOTOOLCHAIN=local go build ./...`、`go test ./...`、`go vet ./...`、`git diff --check`。 - 结果:Go 1.23.0 全部通过;真实 25 MB SQLite 的 15 张表约 1.67 万行完成迁移并两次核对通过;源文件 SHA-256 前后相同。 - **没验证到的部分**:未连接或修改线上生产 MySQL;线上备份、停写、正式迁移与 Admin 切换由 #81 验证。 ## 遗留问题 - 跨公网逐行迁移约 1.67 万行用时约 9 分钟;线上同机迁移预计更快,但仍应安排停写窗口。 ## 相关提交 - `c296fea` 实现 SQLite 到 MySQL 单向迁移