================================================================================ EasyGoAdmin 后端开发者文档 · 08 数据库迁移(整库迁移器) ================================================================================ 代码位置:internal/migrate/{migrate,model,introspect,translate,ddl,copy,conn}.go 入口:cmd/migrator/main.go(纯 CLI,也可 go build 后用 migrator.exe) -------------------------------------------------------------------------------- 1. 定位与原则 -------------------------------------------------------------------------------- 源库内省(表/列/索引/注释)→ 中间表示(IR)→ 目标方言 DDL → 分批搬运数据 → 自增序列修复 → 逐表行数校验。 关键原则: * 独立于业务基座:不依赖 dao/logic,gdb.New 动态连接源库与目标库, 裸读裸写,全程不触发全局钩子(租户隔离/软删/审计盖章),数据原样落库。 * 五方言目标渲染器:mysql / pgsql / sqlite / mssql / oracle。 * 无外键迁移(0 个外键),只迁表/列/二级索引/注释/数据。 * 首期实测链路 mysql→pgsql;mssql/oracle 按定稿方言约定实现,待人工校对 (resource/sql 后四份方言 DDL 同样待人工校对)。 -------------------------------------------------------------------------------- 2. CLI 参数(cmd/migrator/main.go) -------------------------------------------------------------------------------- 源库(6 个): -src-type mysql/pgsql/sqlite/mssql/oracle(默认 mysql) -src-host 默认 127.0.0.1 -src-hostport 默认 3306 -src-database 库名(必填;sqlite 填数据库文件路径) -src-username 默认 root -src-password 目标库(6 个): -dst-type 默认 pgsql -dst-host 默认 127.0.0.1 -dst-hostport 默认 5432 -dst-database 库名(必填;sqlite 填文件路径,不存在时自动创建) -dst-username 默认 postgres -dst-password 迁移行为(3 个): -drop-target-first 迁移前清空目标库全部表(不加则目标已有同名表时该表报错跳过) -tables 只迁指定表(逗号分隔白名单,留空迁全部) -batch-size 数据搬运批大小(默认 1000;Options 注释默认 500,CLI 传 1000) 示例(document/数据迁移命令.txt 有完整版): go run ./cmd/migrator \ -src-type=mysql -src-host=127.0.0.1 -src-hostport=3306 \ -src-database=easygoadmin.goframe.elevue -src-username=root -src-password=root \ -dst-type=pgsql -dst-host=127.0.0.1 -dst-hostport=5432 \ -dst-database=easygoadmin.goframe.elevue -dst-username=postgres -dst-password=123456 \ -drop-target-first -------------------------------------------------------------------------------- 3. 主流程五阶段(migrate.Run) -------------------------------------------------------------------------------- [1/5] 建目标库(ensureDatabase): mysql(维护库空串→information_schema 检查,CREATE DATABASE utf8mb4)、 pgsql(维护库 postgres,CREATE DATABASE)、mssql(维护库 master)、 oracle/sqlite 无需预建。 [2/5] 建表结构: 逐表内省(introspectTable)→ 非清空模式下目标已有同名表则该表失败跳过 (建表语句为 IF NOT EXISTS,数据会追加,必须前置拦截)→ CreateTable → 逐个 CreateIndex。统计表数/索引数。 [3/5] 逐表迁移数据(copyTable,见第 5 节),打印每批进度。 [4/5] 重置自增序列(FixSequence):pgsql setval 到 MAX(id)+1; mssql DBCC CHECKIDENT RESEED;mysql/sqlite/oracle 自动续接无需处理。 [5/5] 逐表行数校验:源/目标 COUNT 对比,输出 PASS/FAIL。 汇总 Report{TableTotal, TableOK, TableFailed, RowTotal, Failed[]}; 存在失败表或行数不一致 → 返回错误、退出码 1。 3.1 五阶段流程图 ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ 1/5 建目标库│──▶│ 2/5 建表结构│──▶│ 3/5 搬运数据│──▶│ 4/5 序列修复│──▶│ 5/5 行数校验│ │ ensureDB │ │ 内省→建表 │ │ 分批 INSERT│ │ setval等 │ │ COUNT 对比 │ │ (不存在才建)│ │ →建索引 │ │ 进度回调 │ │ │ │ PASS/FAIL │ └────────────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ drop-first: 无主键表报错跳过 pgsql/mssql 专项 不一致→ 先 DropAllTables 其余方言免修 整体失败 非清空: 同名表拦截跳过 3.2 数据搬运循环图(doCopy,主键游标分页) lastPK=nil │ ▼ ┌──────────────────────────────────────────┐ │ SELECT 列集 FROM src.table │ │ WHERE pk > lastPK ORDER BY pk LIMIT N │◀──────┐ └──────────────┬───────────────────────────┘ │ 行数=0?─ 是 ─▶ 返回 total │ 否 │ ▼ │ batch=[{列:值(字节转字符串)}…],lastPK=末行 pk ────┘ │ ▼ 目标写入:通用方言 → 参数化多行 INSERT mssql → 内联字面量 INSERT(自增列:事务内 IDENTITY_INSERT ON/OFF) │ ▼ total += len(batch);onBatch 进度回调;len(batch) lastPK ORDER BY pk LIMIT batchSize), 目标端多行 INSERT;每批回调进度;字节切片值转字符串(convertValue)。 方言差异: 通用方言(mysql/pgsql/sqlite/oracle):gdb 参数化多行 Insert (dst.Model(table).Data(batch).Insert())。 mssql 专项:内联字面量 INSERT(buildInsertSQL + inlineValue)绕过 2100 参数占位符上限;含自增列时每批独立小事务 SET IDENTITY_INSERT ON → INSERT → OFF → COMMIT(事务粒度=单批,避免整表单事务撑爆事务日志,同时钉住连接)。 inlineValue:nil→NULL,字符串/字节转义单引号,数值原样,bool→1/0, 时间格式 "2006-01-02 15:04:05"。 源端标识符引用 quoteAll 按源方言(mysql 反引号 / mssql 方括号 / 其余双引号)。 -------------------------------------------------------------------------------- 6. 目标方言 DDL 渲染(ddl.go) -------------------------------------------------------------------------------- Renderer 接口:Quote / Column / CreateTable / CreateIndex / DropAllTables / FixSequence;NewRenderer(dialect) 工厂。 引号:mysql 反引号;pgsql/sqlite/oracle 双引号;mssql 方括号。 默认值 defaultLiteral:时间函数按方言归一(mssql→GETDATE(),oracle→SYSDATE, 其余→CURRENT_TIMESTAMP);数值原样;NULL 保留;其余加引号转义。 各方言要点: mysql AUTO_INCREMENT + NOT NULL;列/表注释 COMMENT 内联; DropAllTables 先 SET FOREIGN_KEY_CHECKS=0;FixSequence 免责。 pgsql GENERATED BY DEFAULT AS IDENTITY;表/列注释 COMMENT ON 独立语句; 索引 CREATE [UNIQUE] INDEX IF NOT EXISTS; DropAllTables CASCADE;类型映射:int→INTEGER、datetime→TIMESTAMP、 blob→BYTEA、json→JSONB、enum/set→VARCHAR(255)(pgsql 无内建枚举, 值域由应用层保证); FixSequence:SELECT setval(pg_get_serial_sequence(...), MAX(id)+1, false)。 sqlite INTEGER PRIMARY KEY AUTOINCREMENT(单列主键列级;复合主键表级约束); 类型极简:整数族/时间→INTEGER,浮点小数→REAL,blob→BLOB,其余 TEXT; 无表/列注释;DropAllTables 关 PRAGMA foreign_keys 再删;免修序。 mssql IDENTITY(1,1);类型走 NVARCHAR/NCHAR(Unicode)、datetime→DATETIME2、 blob→VARBINARY(MAX)、text/json/enum→NVARCHAR(MAX); 建表 IF OBJECT_ID(...) IS NULL CREATE TABLE;注释走扩展属性 sp_addextendedproperty MS_Description; 索引 IF NOT EXISTS(sys.indexes) 保护;FixSequence DBCC CHECKIDENT RESEED。 oracle IDENTITY;NUMBER 族(int→NUMBER(10)、bigint→NUMBER(19))、 NVARCHAR2/NCLOB/BLOB、时间→DATE/TIMESTAMP; 注释 COMMENT ON;无 IF NOT EXISTS(由编排层预检索引存在性,当前 实现直接执行);IDENTITY 缓存自动续接,FixSequence 免责。 -------------------------------------------------------------------------------- 7. 连接管理(conn.go) -------------------------------------------------------------------------------- openConn:gdb.New(gdb.ConfigNode{Name:"migrate_src|migrate_dst", Type, Link}) 注册独立命名节点,不触碰 config.yaml 的默认连接;Query "SELECT 1" 探活。 buildLink 按方言组装(mysql 带 charset/parseTime/loc,pgsql sslmode=disable, mssql encrypt=disable,oracle 简洁串,sqlite sqlite::@file(路径))。 -------------------------------------------------------------------------------- 8. 与 resource/sql 的关系 -------------------------------------------------------------------------------- resource/sql/{mysql,pgsql,sqlite,mssql,oracle}.sql 为“定稿 DDL” (mysql 为规范基准,人工导入维护);迁移器是“运行期动态等价物”。 两者独立演进:新表/新索引需两边同步(DDL 脚本手工维护;迁移器自动跟随 源库结构)。document/*.sql 与 resource/sql 之间存在已知 DDL 漂移 (如部分 uk_code 唯一索引缺失),以 resource/sql 定稿为准。 -------------------------------------------------------------------------------- 9. 实测记录与已知限制 -------------------------------------------------------------------------------- document/ 下保留实测日志:数据迁移PostgreSQL版本日志.txt、 数据迁移SqlServer版本日志.txt、数据迁移Sqlite版本日志.txt、 数据迁移命令.txt(参数说明与示例)。 根目录 migrator.exe 为编译产物。 限制清单: * 无外键/视图/存储过程/触发器迁移 * 无主键表拒绝搬运 * mssql/oracle 源端索引内省未实现(作为源库时二级索引会丢) * 逐表串行搬运(无并发),超大库耗时线性增长 * -drop-target-first 清空的是目标库“全部表”(含非本项目表),慎用 * Oracle 方言差异(如自增回读 LastInsertId)由后续人工处理