diff --git a/docs/task/80-sqlite单向迁移.md b/docs/task/80-sqlite单向迁移.md new file mode 100644 index 0000000..4f3d8c1 --- /dev/null +++ b/docs/task/80-sqlite单向迁移.md @@ -0,0 +1,57 @@ +# 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 单向迁移