华为云信创迁移实战:MySQL/Oracle到openGauss平滑迁移全指南
🚀 华为云信创迁移实战:MySQL/Oracle到openGauss平滑迁移全指南
📝 文章摘要:本文记录了作者在实际项目中,将 MySQL 5.7 和 Oracle 11g 两套业务系统迁移到 openGauss 5.0 的全过程。对比了 chameleon 和 DataKit 两种迁移工具的适用场景、性能和踩坑点,总结了 SQL 兼容性处理、数据类型映射、增量同步延迟控制等核心难点。文末附迁移检查清单和性能对比数据,适合正在做信创数据库替换的团队参考。
⏱ 预计阅读时间:18 分钟
🎯 背景:为什么我们要从 MySQL/Oracle 迁移到 openGauss?
2025 年初,我所在的公司启动了信创改造项目。核心任务是把两个生产系统迁移到国产数据库:
| 系统 | 原有数据库 | 数据量 | 日均 QPS | 迁移目标 |
|---|---|---|---|---|
| 订单中心 | MySQL 5.7 | 约 800GB,2 亿行 | 3,500+ | openGauss 5.0 |
| 财务系统 | Oracle 11g R2 | 约 1.2TB,3.5 亿行 | 1,200+ | openGauss 5.0 |
需求很明确:
- 必须零数据丢失,双跑验证期至少 2 周
- RTO < 30 分钟,回滚方案要提前验证
- 性能不能比原来差,尤其是订单中心的写入链路
选 openGauss 而非 GaussDB,主要原因是:我们团队对 PostgreSQL 生态比较熟悉,openGauss 兼容 PostgreSQL 协议,学习成本相对低,而且是开源版本,云上云下都能部署。
🔍 迁移工具选型:chameleon vs DataKit
openGauss 官方推荐两种迁移工具。我们一开始全量用 chameleon 做,踩了不少坑;后来 DataKit 出了图形化版本,增量部分切了过去。
chameleon
# 安装(Python 3.8+ 环境要求)
pip install openGauss-tools-chameleon
# 初始化迁移配置
chameleon set_config_file mysql_to_opengauss.yaml
# 执行全量迁移
chameleon start_migration mysql_to_opengauss.yaml
# 开始持续复制
chameleon start_replica mysql_to_opengauss.yaml
优点:开源、命令行可控、支持断点续传,适合脚本化批量迁移。
缺点:配置项散落在 YAML 里,类型映射表需要手动维护,遇到异常报错信息比较晦涩。
DataKit
# 下载 DataKit
wget https://opengauss.obs.cn-south-1.myhuaweicloud.com/datakit/5.0.0/datakit-5.0.0-linux-x86_64.tar.gz
tar -xzf datakit-5.0.0-linux-x86_64.tar.gz
cd datakit/bin
./datakit --start # 启动 Web 管理页面,默认端口 8449
优点:图形化配置、自动类型映射、自带数据校验对比、增量同步可视化。
缺点:对于 50GB+ 的大表,首次全量阶段偶尔会 OOM,需要调 JVM 参数。
🏗️ 迁移架构设计

我们采用的是 全量 + 增量 + 双跑验证 三阶段策略:
- T 日:停止源库写入 → chameleon 执行全量导出导入
- T+1 ~ T+14:DataKit 开启增量同步,业务流量同时写入源库和目标库,对比结果
- T+15:确认无差异 → 切换读流量 → 再观察 3 天 → 切换写流量
🛠️ 实施步骤与踩坑记录
Step 1:全量迁移 — chameleon
chameleon 的配置主要在 YAML 里定义源端和目标端的连接、表映射、类型转换规则。
# mysql_to_opengauss.yaml(核心部分)
source:
type: mysql
host: 192.168.1.100
port: 3306
user: migrate_user
password: migrate_pass
database: order_db
tables:
- orders
- order_items
- users
- products
target:
type: opengauss
host: 192.168.2.100
port: 5432 # openGauss 默认端口
user: omm
password: og_pass
database: order_db
type_mapping:
mysql.tinyint(1): boolean
mysql.datetime: timestamp without time zone
mysql.decimal(10,2): numeric(10,2)
replica:
mode: log_based
checkpoint_interval: 30
💥 踩坑 1:tinyint(1) 被当作 boolean
现象:MySQL 中 is_active tinyint(1) 迁移到 openGauss 后变成了 boolean,Java 应用中 getInt() 强转抛出 ClassCastException。
排查:翻 chameleon 日志发现自动类型映射把 tinyint(1) 当作 boolean。这是 MySQL JDBC 驱动的历史问题 —— tinyint(1) 默认被映射成 Boolean。
解决:在类型映射配置中显式指定:
type_mapping:
mysql.tinyint(1): smallint # 不是 boolean,是 smallint!
跑完后手动验证一遍全表的字段类型:
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'orders';
💥 踩坑 2:1.8 亿行的 orders 表迁移到一半卡死
现象:orders 表数据量太大,chameleon 单线程导出到第 8000 万行左右报 killed。
排查:chameleon 默认的 fetch 机制是游标读取,但是插到 openGauss 是单线程 INSERT,写不赢读的,大量行堆积在内存里,被 OOM killer 干掉了。
# dmesg 看到的真相
[86310.123456] oom-killer: gfp_mask=0xcc0(GFP_KERNEL), order=0, pid=23456 (chameleon)
解决:拆分成按 ID 范围分批导出,每批 500 万行:
# 分批脚本
for i in $(seq 0 36); do
offset=$((i * 5000000))
chameleon start_migration mysql_to_opengauss.yaml \
--table orders \
--where "id > ${offset} AND id <= $((offset + 5000000))"
done
同时调整 chameleon JVM 参数(DataKit 也是 Java 写的):
export JAVA_OPTS="-Xms4g -Xmx8g -XX:+UseG1GC"
每批跑完后手动 VACUUM ANALYZE 一下,防止事务膨胀。
Step 2:增量同步 — DataKit
全量迁移完后,我们用 DataKit 开启增量同步。MySQL 端开启 Binlog,Oracle 端使用 LogMiner。
# MySQL 开启 Binlog(my.cnf 配置)
server-id = 100
log_bin = mysql-bin
binlog_format = ROW # 必须 ROW 模式
binlog_row_image = FULL # 必须 FULL
expire_logs_days = 7
# Oracle 开启归档模式
SQL> ALTER DATABASE ARCHIVELOG;
SQL> ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (ALL) COLUMNS;
DataKit 配置增量任务:
# datakit_incremental.yaml
task_name: order_inc_sync
source:
type: mysql
connector: binlog
host: 192.168.1.100
port: 3306
start_position:
file: mysql-bin.000245
pos: 12893745
target:
type: opengauss
filters:
- type: dml
tables:
- order_db.orders
- order_db.order_items
增量同步延迟监控:
-- 在 openGauss 端查看同步延迟
SELECT now() - last_received_heartbeat AS sync_delay_seconds
FROM datakit_heartbeat;
💥 踩坑 3:主键冲突的暗雷
现象:双跑期间,应用的写流量同时发给 MySQL 和 openGauss,但 openGauss 报了主键冲突。
排查:发现 MySQL 的 AUTO_INCREMENT 自增步长和 openGauss 的序列不一致。MySQL 是 AUTO_INCREMENT = 1, INCREMENT = 1,但 openGauss 的序列默认 cache = 20,在双写情况下两边分配的 ID 互相冲突。
解决:把 openGauss 序列的 cache 设为 1,并设置起始值大于 MySQL 当前最大值:
-- 停用原有序列,重建
DROP SEQUENCE IF EXISTS orders_id_seq;
CREATE SEQUENCE orders_id_seq
START WITH 200000000 -- 大于 MySQL 当前最大 ID
INCREMENT BY 1
CACHE 1; -- 不用缓存,避免双写冲突
ALTER TABLE orders ALTER COLUMN id SET DEFAULT nextval('orders_id_seq');
💥 踩坑 4:Oracle 的 NULL 和空字符串
现象:Oracle 迁移到 openGauss,varchar2 字段值为 ''(空字符串)的迁移后变成了 NULL,业务侧部分接口逻辑判断出错。
原因:Oracle 把空字符串当作 NULL,但 openGauss(遵循 PostgreSQL 语义)区分空字符串和 NULL。迁移时默认行为是一致的,但有些字段在 Oracle 端本身就是 '',被当作 NULL 插入了,业务没预期。
解决:对关键 varchar2 字段在迁移后的 openGauss 上做补丁:
UPDATE financial_records
SET remark = ''
WHERE remark IS NULL
AND original_source = 'oracle';
当然更好的办法是在迁移前做数据清洗,把所有''显式标记出来。我们当时时间紧,只能事后补。
Step 3:数据校验
迁移完不验证等于白做。我们用 DataKit 自带的数据校验功能和自写脚本双重确认。
-- 行数比对(最简单的兜底)
SELECT 'mysql' as source, COUNT(*) as row_count FROM orders
UNION ALL
SELECT 'opengauss', COUNT(*) FROM orders;
-- Checksum 校验(更严谨)
-- MySQL 端
SELECT SUM(CRC32(CONCAT_WS(',', id, user_id, total_amount, status, created_at)))
FROM orders;
-- openGauss 端(MySQL 的 CRC32 函数在 openGauss 里没有,用 md5 代替)
SELECT SUM(CAST(('x' || md5(CONCAT_WS(',', id::text, user_id::text,
total_amount::text, status, created_at::text))) AS BIT(32))::BIGINT)
FROM orders;

📊 性能对比:迁移前后
订单中心(MySQL → openGauss)
| 指标 | MySQL 5.7 | openGauss 5.0(默认) | openGauss 5.0(调优后) |
|---|---|---|---|
| TPS(写入,32 并发) | 8,500 | 7,200 ↓ 15% | 9,800 ↑ 15% |
| QPS(复杂查询,16 并发) | 3,200 | 3,800 ↑ 19% | 5,200 ↑ 63% |
| P99 延迟(写入) | 45ms | 52ms | 38ms |
| 备份速度(全量) | 23 min | 19 min ↑ 17% | 19 min |
调优参数(鲲鹏服务器上实测有效):
# postgresql.conf 核心参数
max_connections = 2000
shared_buffers = 8GB # 物理内存的 25%
work_mem = 64MB
maintenance_work_mem = 2GB
effective_cache_size = 24GB
# openGauss 特有参数
enable_nestloop = off # 强制走 hash join,对 OLTP 更友好
enable_seqscan = off # 强制走索引扫描
cstore_insert_mode = fast # 列存储快速导入模式
numa_distribute_mode = 'all' # 鲲鹏 NUMA 亲和调度
财务系统(Oracle → openGauss)
| 指标 | Oracle 11g | openGauss 5.0(调优后) |
|---|---|---|
| 月度结算跑批(存储过程) | 47 min | 41 min ↑ 13% |
| 复杂报表查询(多表 JOIN) | 8.2s | 6.5s ↑ 21% |
| 数据导入(1 亿行,COPY 方式) | 12 min | 9 min ↑ 25% |
Oracle 的 PL/SQL 存储过程迁移到 openGauss 的 P/L 函数是比较痛苦的,我们踩了一些函数兼容性的坑,这部分会在第二篇单独讲。
❓ 常见问题
Q1:chameleon 和 DataKit 到底选哪个?
建议:全量用 chameleon(脚本可控)+ 增量用 DataKit(可视化监控)。如果数据量小于 100GB,只用 DataKit 就够了。如果表数量超过 500 张,建议 chameleon + 分批脚本。
Q2:迁移过程中业务可以不停机吗?
对于 MySQL,理论上有 CDC 工具可以实现接近零停机。但我们实践下来,至少需要 15-30 分钟的只读窗口来完成全量快照的一致性保证。Oracle 同理。
Q3:openGauss 的序列和 MySQL auto_increment 行为不同,需要注意什么?
MySQL 的自增 ID 在事务回滚后不会回退,但 openGauss 序列默认 cache = 20,重启后会跳过一截。线上业务如果对 ID 连续性有强要求(比如作为排序依据),需要做额外处理。
Q4:迁移后发现性能反而下降了,怎么办?
先做 WDR 报告,看瓶颈在哪里:
-- 在 openGauss 主库执行
SELECT * FROM dbe_perf.statement_history
WHERE start_time > now() - interval '1 hour'
ORDER BY duration DESC LIMIT 20;
常见原因:缺失索引、统计信息陈旧、NUMA 未亲和、seqscan 没有禁用。按上文的参数配置调优大多能解决。
✅ 迁移检查清单(可直接复用)
【迁移前】
□ 源库表结构导出,检查字段类型、自增列、默认值
□ 字符集统一(推荐 UTF-8)
□ 确认所有表都有主键(CDC 工具需要)
□ 评估数据量,选择工具类型
【迁移中】
□ 全量阶段分批执行(每批不超过 5GB)
□ 增量阶段监控延迟 < 5s
□ 类型映射手动校验(特别是 tinyint、datetime、enum)
□ 定期 VACUUM ANALYZE,防止膨胀
【迁移后】
□ 行数 + Checksum 双重校验
□ 业务回放压测(至少 1 小时,覆盖所有核心接口)
□ 双跑验证不少于 7 天(推荐 14 天)
□ 备份策略更新(openGauss 用 gs_dump 或 OBS 归档)
□ 监控告警切换(AOM 接入 openGauss 指标)
📝 总结
这次迁移最深的几点体会:
-
工具只是辅助,类型映射才是核心。chameleon 和 DataKit 都能跑通基本流程,但真正花时间的全是在处理 tinyint(1)、datetime 精度、空字符串这些"小事"。
-
双跑机制不能省。14 天的双跑帮我们抓到了 7 处业务逻辑差异,大部分是日期处理和 NULL 判断的边界问题。如果直接切过去,上线后排查指定崩溃。
-
鲲鹏 + openGauss 的组合在 OLAP 场景下提升明显。复杂查询比 MySQL 快 63%,比 Oracle 快 21%。但 OLTP 写入需要调优,默认配置下甚至不如 MySQL 5.7。
-
存储过程迁移是最痛苦的。Oracle 的 PL/SQL 和 openGauss 的 P/L 函数是"看起来像,用起来吐血"的关系,这部分我们花了整个项目 1/3 的时间,下一篇文章专门写。
💬 互动:你们团队在做信创数据库迁移吗?用的是 openGauss 还是其他国产数据库?评论区聊聊踩过的坑。
- 点赞
- 收藏
- 关注作者
评论(0)