208 lines
13 KiB
Plaintext
208 lines
13 KiB
Plaintext
================================================================================
|
||
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)<N → 结束
|
||
|
||
--------------------------------------------------------------------------------
|
||
4. 源库内省与中间表示
|
||
--------------------------------------------------------------------------------
|
||
表清单:db.Tables + 白名单过滤 + 按名排序(结果可复现)。
|
||
列:TableFields 按 Index 还原物理列序;类型经 normalizeType 归一为中间类型
|
||
(20 种:bool/tinyint/smallint/int/bigint/float/double/decimal/varchar/char/
|
||
text/tinytext/mediumtext/longtext/date/time/datetime/blob/json/enum/set/other),
|
||
同时解析长度/精度/枚举值;剥离 unsigned 与 pgsql 时区修饰;
|
||
tinyint(1) 惯例归为 bool;uuid→char(36)。
|
||
默认值 cleanDefault:pgsql 剥 `'x'::character varying` 类型转换、
|
||
current_timestamp() 等函数归一;其余方言原样。
|
||
自增识别 isAutoInc:extra 含 auto_increment 或 identity。
|
||
索引:MySQL SHOW INDEX / pgsql pg_indexes(解析 indexdef 括号内列清单,
|
||
跳过表达式索引部分);主键并入列元数据 IsPK,其余为二级索引;
|
||
其余三方言二级索引降级跳过。
|
||
索引命名统一 normalizeIndexName:唯一 uk_ / 普通 idx_ + 表名 + 列名,
|
||
超 63 字符截断(迁移后全库一种命名规则)。
|
||
|
||
IR 结构(model.go):
|
||
TableMeta{Name, Comment, Columns[]ColumnMeta, Indexes[]IndexMeta}
|
||
ColumnMeta{Name, Type(中间类型), Length, Scale, EnumVals, Nullable, Default,
|
||
AutoInc, Comment, IsPK}
|
||
IndexMeta{Unique, Name, Columns}
|
||
|
||
--------------------------------------------------------------------------------
|
||
5. 数据搬运(copy.go)
|
||
--------------------------------------------------------------------------------
|
||
无主键表直接报错(“请先补主键”)——分页搬运依赖主键排序。
|
||
核心循环 doCopy:
|
||
源端按主键排序快照分页(WHERE pk > 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)由后续人工处理
|