
每一个测试用例背后,都是一次真实迁移场景的缩影
MySQL 要迁到 PostgreSQL,表面上只是换个数据库,实际上:
AUTO_INCREMENT 要改成 SERIALIFNULL、GROUP_CONCAT、JSON 函数全部要转换只要有一个环节出错,数据就可能丢、查不出、或者写不进去。
今天介绍的这个开源项目 MySQL2PG,用一套全覆盖的测试体系 + CI 自动化流水线,使用大量的测试案例,把"心跳迁移"变成了"安心迁移",确保了迁移期间无异常,顺利过度。
create_table.sql 文件包含了 167 个测试表案例,按覆盖范围分为 5 大类:
覆盖数值、字符集/排序规则、JSON、时间、默认值、自增、约束、生成列、保留字、命名风格等。
测试范围 | 表案例 | 覆盖内容 |
|---|---|---|
整数类型 | case_01 | tinyint/smallint/mediumint/int/bigint/integer,含精度变体 |
布尔类型 | case_02 | TINYINT(1) → BOOLEAN,大小写不敏感 |
浮点数类型 | case_03 | float/double/decimal/numeric/real,含精度和标度 |
字符集类型 | case_04~07 | utf8/utf8mb4/latin1/utf16/ascii,含排序规则 |
JSON类型 | case_08 | json字段,支持嵌套结构 |
日期时间类型 | case_09 | date/time/datetime/timestamp/year,含精度变体 |
默认值变体 | case_10 | 数值默认值、字符串默认值、CURRENT_TIMESTAMP |
自增类型 | case_11 | AUTO_INCREMENT,多自增字段处理 |
无符号类型 | case_12 | unsigned/zerofill,无符号整数转换 |
枚举和集合 | case_13 | enum/set → VARCHAR(255) |
二进制类型 | case_14 | binary/varbinary/blob/longblob/mediumblob/tinyblob → BYTEA |
表选项 | case_15 | ROW_FORMAT/COLLATE/CHARSET 等表级选项 |
分区表 | case_16 | RANGE 分区,按年份分区 |
临时表 | case_17 | TEMPORARY TABLE 处理 |
引号标识符 | case_18 | 反引号引用标识符 |
注释类型 | case_19 | 列注释和表注释 |
约束类型 | case_20 | PRIMARY KEY/UNIQUE KEY/INDEX 复合约束 |
虚拟列 | case_21 | GENERATED ALWAYS AS VIRTUAL |
空间类型 | case_22 | geometry/point/linestring/polygon 等 |
怪异语法 | case_23 | INTEGER(10)/DOUBLE PRECISION(10,2) 等非标准语法 |
边缘情况 | case_24 | 混合字符集、自增主键、longblob |
MySQL 8.0保留字 | case_25 | rank/system/groups/window/function/role/admin |
不可见列 | case_26 | INVISIBLE COLUMN + 不可见索引 |
检查约束 | case_27 | CHECK (age > 18) 约束 |
函数索引 | case_28 | MySQL 8.0 表达式索引 |
默认值变体 | case_29 | char/json 默认值 |
字符集和排序规则 | case_30 | utf8mb4_general_ci/utf8mb4_bin |
系统表模拟 | case_31 | 模拟 mysql.db 表结构 |
复杂生成列 | case_32 | CASE WHEN 表达式的生成列 |
降序索引 | case_33 | DESC 索引,混合方向主键 |
表选项 | case_34 | ENGINE=InnoDB 显式指定 |
枚举字符集 | case_35 | enum/set 带字符集和排序规则 |
大写表名 | case_36 | UPPERCASE 表名和列名 |
驼峰命名 | case_37 | ProductId/ProductName/LastUpdate |
蛇形命名 | case_38 | product_id/product_name/last_update |
下划线命名 | case_39 | 带下划线的命名风格 |
默认值 | case_40 | 各种默认值变体 |
覆盖外键、全文、空间、复合主键、存储引擎、分区、复制建表、压缩、统计信息等。
覆盖SRID、长标识符、高精度数值、多值索引、窗口函数、JSON_TABLE、锁相关语法等。
覆盖电商、CMS、财务、社交、日志、医疗、酒店、餐厅等真实业务场景。
覆盖复合外键、JSON生成列、时间类型组合、文本二进制混合、数值边界、建表方式专项。
create_comments_partition_table.sql 文件专门测试分区表的迁移兼容性:
-- 特点:
-- 1. 基础 RANGE 分区示例,仅包含一个分区
-- 2. 主键必须包含分区键 issue_id
-- 3. 使用 ENGINE = InnoDB 指定存储引擎
-- 4. 适用于按整数范围进行简单数据划分的场景
-- 5. row_format=dynamic 支持动态行格式-- 特点:
-- 1. 经典 RANGE 分区模式,包含 5 个分区
-- 2. 分区范围递增:1000, 2000, 3000, 10000, MAXVALUE
-- 3. 使用 MAXVALUE 作为最后一个分区的边界
-- 4. 主键包含分区键,满足 MySQL 分区约束
-- 5. 适用于按连续整数范围均匀分布数据的场景
-- 6. 常用于时间序列数据、ID 范围分片等场景-- 特点:
-- 1. LIST 分区模式,按离散值列表进行分区
-- 2. 分区键为状态字段,支持业务状态分类
-- 3. 分区值为离散集合:p0(0), p1(1), p2(2,3)
-- 4. 一个分区可包含多个离散值(如 p2 包含 2 和 3)
-- 5. 主键必须包含分区键 status
-- 6. 适用于按枚举值、状态码等离散值进行数据划分
-- 7. 注意:LIST 分区不支持 DEFAULT 分区,插入不在列表中的值会报错-- 特点:
-- 1. RANGE 分区模式,包含 5 个分区
-- 2. 分区范围呈指数增长:1000, 5000, 10000, 50000, MAXVALUE
-- 3. 非均匀分区策略,适合数据分布不均匀的场景
-- 4. 早期分区范围小,后期分区范围大
-- 5. 包含 TEXT 类型字段,测试大字段在分区表中的兼容性
-- 6. 主键包含分区键 issue_id
-- 7. 适用于数据量随时间增长的业务场景create_unique_key_table.sql 文件专门验证唯一键约束在迁移时的兼容性:
create_index.sql 文件包含以下索引类型:
create_view.sql 定义了 42 个测试视图,按复杂度分为 5 个等级:
create_function.sql 定义了 110 个复杂的存储函数,每个函数都有明确的测试目标:
每个函数 30~200 行代码,覆盖了 MySQL 存储过程的核心语法。
有了测试用例还不够,怎么确保每次代码提交都不会破坏迁移功能?
MySQL2PG 的 GitHub Actions CI 流水线给出了答案:
CI 流水线按顺序执行 10 种数据库版本组合,避免资源竞争:
MySQL 5.7 → PostgreSQL 12 ↓
MySQL 5.7 → PostgreSQL 14 ↓
MySQL 5.7 → PostgreSQL 16 ↓
MySQL 5.7 → PostgreSQL 17 ↓
MySQL 5.7 → PostgreSQL 18 ↓
MySQL 8.0 → PostgreSQL 12 ↓
MySQL 8.0 → PostgreSQL 14 ↓
MySQL 8.0 → PostgreSQL 16 ↓
MySQL 8.0 → PostgreSQL 17 ↓
MySQL 8.0 → PostgreSQL 18每个 Job 都包含以下步骤:
1. 启动MySQL容器(5.7或8.0)
2.启动PostgreSQL容器(12/14/16/17/18)
3.等待两个数据库就绪(healthcheck)
4.执行SQL脚本初始化测试数据:
├──create_table.sql # 167 张表
├──insert_data.sql # 每张表 10 条测试数据
├──create_index.sql # 数百个索引
├──create_view.sql # 42 个视图
├──create_function.sql # 110 个函数
├──create_user.sql # 用户和权限
└──create_comments_partition_table.sql# 分区表注释
5.生成config.yml配置文件
6.编译并运行mysql2pg工具
7. 上传转换日志(失败时)CI 流水线确保:
MySQL2PG 的转换能力可以总结为以下几个维度:
迁移完成后,MySQL2PG 会自动执行数据验证:
✅ 转换表结构:167 张表
✅ 同步表数据:1670 行
✅ 转换索引:数百个
✅ 转换视图:42 个
✅ 转换函数:110 个
✅ 数据验证:MySQL 行数 = PostgreSQL 行数如果任何一张表的行数不一致,CI 流水线会立即失败,阻止有问题的代码合并。
有了全面的测试用例还不够,代码本身的稳定性和可靠性如何保障?
MySQL2PG 建立了三层测试体系,确保每一行代码都经过严格验证:
单元测试是代码质量的第一道防线,覆盖核心转换逻辑的每个细节:
1. 视图函数转换测试(88+ 测试用例)
// 示例:IFNULL 转换测试
tests := []struct{
name string
input string
expected string
}{
{"IFNULL 单参数", "IFNULL(a)", "COALESCE(a)"},
{"IFNULL 双参数", "IFNULL(a, b)", "COALESCE(a, b)"},
{"IFNULL 嵌套", "IFNULL(IFNULL(a, b), c)", "COALESCE(COALESCE(a, b), c)"},
{"IFNULL 在视图中", "SELECT IFNULL(col, 0) FROM t", "SELECT COALESCE(col, 0) FROM t"},
}2. 数值函数转换测试
// ROUND/MOD 转换测试
tests := []struct{
name string
input string
expected string
}{
{"ROUND 单参数", "ROUND(col, 2)", "ROUND(col::NUMERIC, 2)"},
{"MOD 单参数", "MOD(col, 10)", "MOD(col::NUMERIC, 10)"},
{"ROUND 嵌套", "ROUND(ROUND(col, 3), 2)", "ROUND(ROUND(col::NUMERIC, 3)::NUMERIC, 2)"},
}3. 日期时间函数转换测试
// DATE_FORMAT 转换测试
tests := []struct{
name string
input string
expected string
}{
{"DATE_FORMAT 简单", "DATE_FORMAT(dt, '%Y-%m-%d')", "to_char(dt, 'YYYY-MM-DD')"},
{"DATE_FORMAT 复杂", "DATE_FORMAT(dt, '%Y-%m-%d %H:%i:%s')", "to_char(dt, 'YYYY-MM-DD HH24:MI:SS')"},
}4. JSON 函数转换测试
// JSON 函数转换测试
tests := []struct{
name string
input string
expected string
}{
{"JSON_EXTRACT", "JSON_EXTRACT(doc, '$.name')", "doc->>'name'"},
{"JSON_OBJECT", "JSON_OBJECT('key', val)", "json_build_object('key', val)"},
{"JSON_ARRAY", "JSON_ARRAY(a, b)", "json_build_array(a, b)"},
}# 运行所有单元测试
go test -v -race -coverprofile=coverage.out ./...
# 按包运行测试
go test ./internal/config/...
go test ./internal/converter/postgres/...
go test ./internal/postgres/...
go test ./internal/report/...
# 查看覆盖率
go tool cover -html=coverage.out -o coverage.html当前代码覆盖率:98%+,确保核心转换逻辑的每一行代码都被测试覆盖。
集成测试在真实的 MySQL 和 PostgreSQL 环境中验证端到端的迁移流程:
#!/bin/bash
# 脚本:scripts/integrationtests/run_integration_tests.sh
# 1. 修改 config.yml 配置
cat > config.yml << EOF
mysql:
host: 127.0.0.1
port: 3306
username: root
password: rootpassword
database: test_db
postgresql:
host: localhost
port: 5432
username: postgres
password: postgrespassword
database: test_db
conversion:
options:
tableddl: true
data: true
view: true
indexes: true
functions: true
users: true
table_privileges: true
validate_data: true
EOF
# 2. 运行 MySQL2PG 工具
./mysql2pg -c config.yml
# 3. 检查退出码
if [ $? -eq 0 ]; then
echo"✅ PASS"
else
echo"❌ FAIL"
fi
# 4. 验证 PostgreSQL 数据
psql -h localhost -U postgres -d test_db -c "SELECT COUNT(*) FROM case_01_integers;"每个集成测试用例都会验证:
每一次代码提交都会触发 GitHub Actions CI 流水线,自动执行所有测试:
# .github/workflows/go.yml
unit-test:
name:UnitTests
runs-on:ubuntu-latest
steps:
-uses:actions/checkout@v4
-uses:actions/setup-go@v5
with:
go-version:'1.24'
-run:gobuild-v./...
-run:gotest-v-race-coverprofile=coverage.out./internal/config/...
-run:gotest-v-race-coverprofile=coverage.out./internal/converter/postgres/...
-run:gotest-v-race-coverprofile=coverage.out./internal/postgres/...
-run:gotest-v-race-coverprofile=coverage.out./internal/report/...
integration-test-mysql57-pg12:
name:MySQL5.7→PostgreSQL12
needs:unit-test
services:
mysql:
image:mysql:5.7
postgres:
image:postgres:12
steps:
-初始化测试数据(167张表+42视图+110函数)
-运行MySQL2PG工具
-验证迁移结果
integration-test-mysql57-pg14:
name:MySQL5.7→PostgreSQL14
needs:integration-test-mysql57-pg12
# 同样的测试流程
# 共 10 种版本组合1. 代码质量保障
2. 功能验证保障
3. 发布质量保障
┌─────────────────────────────────────────┐
│ 第三层:CI 流水线(10 种版本组合) │
│ • 每次提交自动执行 │
│ • 端到端集成测试 │
│ • 数据验证 │
├─────────────────────────────────────────┤
│ 第二层:集成测试(84 个测试用例) │
│ • 真实数据库环境 │
│ • 完整迁移流程 │
│ • 功能验证 │
├─────────────────────────────────────────┤
│ 第一层:单元测试(98%+ 覆盖率) │
│ • 核心转换逻辑 │
│ • 函数映射验证 │
│ • 边界情况处理 │
└─────────────────────────────────────────┘数据类型 | 数量 | 说明 |
|---|---|---|
测试表 | 167 张 | 覆盖所有 MySQL 类型和语法 |
测试数据 | 1670 行 | 每张表 10 行测试数据 |
测试索引 | 数百个 | 主键、唯一键、普通索引、全文索引 |
测试视图 | 42 个 | 从简单视图到 MySQL 8.0 高级特性 |
测试函数 | 110 个 | 最多涉及 10 表关联的复杂逻辑 |
测试用例 | 84 个 | 集成测试覆盖所有配置选项 |
单元测试 | 200+ | 覆盖核心转换逻辑的每个细节 |
三层测试体系,200+ 单元测试用例,84 个集成测试用例,10 种数据库版本组合,确保每一次代码提交都能稳定迁移。
MySQL2PG 的测试体系可以总结为四句话:
数据库迁移不是赌运气,而是靠体系化的测试、自动化的流程和严格的代码质量保障来确保每一次都能成功。
📦 项目地址:https://github.com/xfg0218/MySQL2PG