Files
EasyGoAdmin_GoFrame_EleVue/document/wiki/08_数据库迁移.txt
T
2026-10-20 11:37:25 +08:00

208 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
================================================================================
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)由后续人工处理