1. 项目概述
1.1 核心需求解析
过去几个月,我一直在忙一个“把 MySQL 换成国产数据库”的项目。简单说,就是把一套跑了好几年的业务系统,从 MySQL 5.7 迁移到国产数据库环境,涉及数据迁移、SQL 语法适配、高可用架构调整,以及周边工具链的替换。
这类项目有个官方名字叫“数据库国产化适配与替代”,不过业内更习惯叫“信创数据库迁移”。核心目标很朴素:底层存储不再依赖 MySQL/Oracle 这类传统闭源或国外主导的数据库,而是换成国产自研的数据库产品,同时保证业务系统几乎无感切换。
这个项目的受众很明确——如果你正在做政府项目、国企系统、金融机构,甚至只是自己在内部系统里想提前尝试国产数据库,那这篇内容会非常有参考价值。哪怕你目前连 MySQL 都没迁移过,只要了解基本的 SQL 和事务概念,也能看懂后面绝大部分内容。
1.2 涉及到的技术栈与范围
这次适配涉及的数据库产品主要有三类:
- 基于 MySQL 内核二次开发的国产数据库(如某疆、某梦的部分分支产品)
- 基于 PostgreSQL 内核二次开发的国产数据库(如人大金仓、瀚高、TeleDB 等)
- 完全自研的分布式数据库(如 OceanBase、GaussDB、TiDB 的国产化分支等)
我这次实际接触的是前两类,一套是某个 MySQL 兼容分支(兼容度约 95%),另一套是 PostgreSQL 系的产品(兼容度约 80%,需要做较多语法调整)。
除了数据库本身,这个项目还牵扯到:
- 数据迁移工具(既有官方工具,也有自研脚本)
- 同步链路(业务系统要求主备实时同步,不是导一次就完事)
- 监控、备份、运维工具的国产化替换
- 应用层 SQL 层面的适配改造
1.3 为什么现在要做国产化
很多人一听到“国产化”就下意识觉得是政策驱动。说实话,政策确实是很大的推动力,尤其是政务、金融这类监管严格的行业。但从技术角度看,有几个实际问题也倒逼着团队去尝试:
- MySQL 版本碎片化严重,5.7 已经 EOL,社区版不再有安全更新,不迁移也得升级
- 商业版授权费用逐年上涨,一套高可用的 MySQL 商业订阅在某些场景下并不便宜
- 随着业务数据量增加,单机 MySQL 的扩展性瓶颈越来越明显
所以这个项目不完全是“情怀工程”,它其实和常规的数据库升级改造很像,只是多了一层“替换数据库品牌”的约束。理解了这一点,后面每一步操作逻辑就都能对上了。
2. 整体设计思路与方案选型
2.1 为什么选择适配而非直接替换
“替换”听起来简单——把 MySQL 停掉,装上新的国产数据库,导入数据,改一下连接串,完事。但真实做过的人都知道,这里面坑最多的地方不是数据库本身,而是围绕 MySQL 生长的整个生态。
举个例子,我们的业务系统里大量使用存存储过程、自定义函数、动态 SQL 拼接。这些语法在 MySQL 里跑得好好的,但换到国产数据库后,可能连基础的分页语法都有差异。还有些第三方报表工具,底层 SQL 是自动生成的,如果数据库语法不兼容,报表直接挂掉。
所以我这次定了一个基调:能不改应用层就不改,先评估核心组件能不能实现“透明替代”,剩下的再逐层适配。
具体来说决策路径是这样的:
- 先梳理应用系统的数据库访问方式(JDBC、ODBC、原生驱动)
- 评估国产数据库的内核兼容度,优先选兼容 MySQL 语法的产品
- 边缘系统可以用兼容度较差但更自主可控的数据库(比如跑在国产 CPU 上的场景)
- 核心交易链路必须保证语法兼容度和事务行为一致
2.2 国产数据库选型对比
业内现在主流的国产数据库选型,基本可以分成几类,我这里列一个相对客观的对比表格:
| 数据库类型 | 内核基础 | MySQL 语法兼容度 | 事务支持 | 适合场景 | 主要风险 |
|---|---|---|---|---|---|
| MySQL 内核分支 | MySQL | 高(约 95%) | ACID | 业务系统快速替换 | 自主可控程度有限 |
| PostgreSQL 内核分支 | PG | 中(约 60%-80%) | ACID | 有 PG 经验的团队 | 改写工作量较大 |
| 自研分布式 | 独立 | 中(约 70%) | 分布式事务 | 超大规模数据场景 | 运维体系不成熟 |
| 云数据库国产版 | 基于开源 | 高 | ACID | 上云客户 | 数据驻留有要求 |
我们这次最终选择了一条混合路线:
- 核心业务库使用某 MySQL 兼容数据库(底层是 MySQL 的某个版本分支)
- 边缘系统使用 PostgreSQL 内核的国产数据库
- 数据仓库层用一套分布式国产库承载报表分析
这个选择的核心逻辑是风险和成本平衡。核心业务库不能冒险,选兼容度最高的;边缘库则可以顺便验证一下新生态;数据仓库场景因为数据量太大,单机 MySQL 本来就不合适,干脆一步到位换分布式。
2.3 架构层面的提前改造
数据库替换不是只换一个软件,它会牵动整个架构。举几个我实际遇到的架构问题:
首先,跨库 Join 的问题。原业务里有两张核心订单表,分别存放在两个 MySQL 实例中,应用层通过代码来合并结果。换成国产库后,其中一套用了 PG 内核的库,另一套是 MySQL 内核,两个实例完全异构,原来的数据合并逻辑反而更麻烦了。
其次,主从同步机制。MySQL 的 binlog 同步大家都很熟,主从切换在业务层面几乎无感。但国产数据库的主从方案各有各的不同,有的用物理复制,有的仅支持逻辑复制,还有的得通过中间件实现。这不仅影响运维习惯,还影响故障时的恢复时间。
所以在真正动数据库之前,我先做了几个前置动作:
- 梳理所有数据库实例的归属、版本、业务等级
- 分析每个业务系统的数据库访问模式(读多写少 vs 高并发写入)
- 把跨库依赖尽量收拢,能合并的实例提前合并
这些工作在项目后期才发现有多值钱。数据迁移本身半天就能完成,但业务系统联调和 SQL 改写花了几周时间,如果没有提前把边界理清,时间会翻倍。
3. 数据迁移与同步方案
3.1 迁移路径设计
这次迁移的核心原则是“不停服、少停机、可回滚”。在实际执行时,我设计了两条迁移路径:
路径一:存量数据初始导入
- 使用数据库官方迁移工具或自研脚本,把历史数据一次性导入目标库
- 导出时统一使用逻辑导出(mysqldump / 自带迁移工具),保证数据格式可控
- 导入前关闭目标库的约束校验和外键检查,导入完成后重新启用
路径二:增量数据实时同步
- 对源库开启 Binlog,由同步工具把变更数据实时传输到目标库
- 提供与源库一致的主键冲突处理策略(替换、忽略、报错)
- 同步过程中同步监控延迟指标,确保数据追上源库
这里建议工具选择上优先用数据库厂商提供的官方迁移工具,因为官方工具对自家数据库的细节最了解,兼容性问题最少。比如某 MySQL 兼容系数据库就提供了图形化的迁移评估工具,可以先扫描一遍建表语句,提前识别不兼容语法。这一步非常推荐,能省掉后期手动排查语法问题的大量时间。
3.2 全量迁移实操细节
执行全量迁移时,有几个关键技术细节是文档不会重点标注的:
先看导出参数。使用 mysqldump 做逻辑导出,要加几个关键参数:
mysqldump -u root -p \ --single-transaction \ --set-gtid-purged=OFF \ --master-data=2 \ --routines \ --triggers \ --databases db_prod > db_prod.sql参数解释:
--single-transaction:基于 InnoDB 的事务快照,保证导出过程中不锁表,业务可以继续写入--set-gtid-purged=OFF:如果目标库不需要 GTID 复制,关掉避免导入报错--master-data=2:记录导出时刻的 binlog 位置,方便后续增量同步--routines和--triggers:导出存储过程、函数和触发器,很多新手漏掉这两个参数,结果迁移完应用一调用就报错
然后处理导入。我先把目标库的几个会话级参数临时调大:
SET GLOBAL foreign_key_checks = 0; SET GLOBAL unique_checks = 0; SET GLOBAL innodb_flush_log_at_trx_commit = 2;导入完成后记得恢复默认值,否则外键约束和数据安全性都会受到影响。
3.3 增量同步实现方案
增量同步这块,我踩过不少坑。不同国产数据库对 Binlog 的解析支持不同,有些数据库甚至连原生的逻辑复制功能都没有完整暴露出来。这里分享两个可落地的路径:
方案一:官方数据迁移工具(推荐)
大多数国产数据库厂商都提供从 MySQL 迁移到自家数据库的完整工具链,通常包含全量迁移 + 增量同步功能。这类工具的优点是:
- 深度理解源库索引、约束、字符集,迁移时保留完整语义
- 增量同步时使用源库 Binlog 主动拉取,不需要手工配置主从
- 提供图形化监控界面,同步延迟一目了然
方案二:中间件 + 自研消费程序
对于有些偏门场景,官方工具覆盖得不够好,比如需要做业务逻辑转换、字段映射、数据脱敏。这时候我会用 Canal(阿里巴巴开源的 Binlog 解析中间件)把变更日志推送到消息队列,然后自行写消费者写入目标库。
代码框架大概是这样的:
// 伪代码:消费 Binlog 变更事件并写入国产数据库 public void handleChange(ChangeEvent event) { if (event.getType() == INSERT) { String sql = buildInsertSql(event.getTableName(), event.getData()); targetDb.execute(sql); } else if (event.getType() == UPDATE) { String sql = buildUpdateSql(event.getTableName(), event.getData(), event.getOldData()); targetDb.execute(sql); } }注意,这种链路要额外处理:事务顺序(同一事务内的变更必须顺序执行)、幂等(消费端重试时不能重复插入)、类型转换(MySQL 的 datetime 与国产库 datetime 的格式差异)等。
3.4 迁移后的数据校验
数据迁移完成后,不要急着让业务切换流量,先做三件事:
- 行数校验:统计每张表的总行数,源库和目标库对比
- 抽样校验:随机抽取若干订单、用户数据,比对关键字段
- 聚合校验:对业务报表中常用的 SUM、COUNT、MAX 做全量验证
其中行数校验最容易出现问题,因为有的表在迁移过程中还在写入,两边统计时间点不一致,或者存在数据修复、删除操作,导致行数对不上。我给到的建议是:校验要基于 binlog 位置来决定校验起点,确保源库和目标库从同一位点开始比对。
4. SQL 语法适配实战
4.1 常见不兼容点
即使是兼容度最高的国产数据库,光靠厂商宣称的“兼容 MySQL”,在实际跑业务时还是会遇到各种语法差异。我在这几个月里遇到的高频问题,整理出来供参考:
| 场景 | MySQL 写法 | 国产库常见问题 |
|---|---|---|
| 分页查询 | LIMIT offset, count | 部分库只支持LIMIT count OFFSET offset,行为略有差异 |
| 自增列 | AUTO_INCREMENT | PG 系数据库使用SERIAL或IDENTITY,建表语句需要转换 |
| 字符串拼接 | CONCAT(a, b) | 部分库支持,但参数类型不同时报错 |
| 布尔类型 | TINYINT(1) | PG 系数据库原生支持 BOOLEAN,查询结果类型不同 |
| 日期函数 | NOW()、CURDATE() | 有些库用CURRENT_TIMESTAMP,需要全局替换 |
| 全文索引 | FULLTEXT INDEX | 部分库不支持或语法差异大 |
| 字段注释 | COMMENT 'xxx' | 建表语句解析规则不同,引号转义易报错 |
4.2 存储过程改造
存储过程改造是本项目最耗时的部分,没有之一。
MySQL 的存储过程语法和 PostgreSQL 系数据库差异巨大,走 PG 内核的国产库时,几乎每个过程都要人工重写。举一个典型例子:
MySQL 写法:
DELIMITER $$ CREATE PROCEDURE `sp_update_order`(IN p_order_id INT) BEGIN DECLARE v_status INT DEFAULT 0; SELECT status INTO v_status FROM orders WHERE id = p_order_id; IF v_status = 0 THEN UPDATE orders SET status = 1 WHERE id = p_order_id; END IF; END$$ DELIMITER ;PG 内核国产库写法:
CREATE OR REPLACE FUNCTION sp_update_order(p_order_id INT) RETURNS VOID AS $$ DECLARE v_status INT DEFAULT 0; BEGIN SELECT status INTO v_status FROM orders WHERE id = p_order_id; IF v_status = 0 THEN UPDATE orders SET status = 1 WHERE id = p_order_id; END IF; END; $$ LANGUAGE plpgsql;表面上只是把 PROCEDURE 变成 FUNCTION,但内部语法机制完全不同:
- MySQL 用
DECLARE声明变量,PG 用DECLARE但位置不同 - MySQL 直接返回结果集,PG 需要通过
RETURN QUERY或游标 - 异常处理语法差异巨大:MySQL 用
DECLARE CONTINUE HANDLER FOR SQLSTATE,PG 用EXCEPTION WHEN others THEN - MySQL 支持动态 SQL 拼接,PG 需要
EXECUTE format(...)表达式
如果业务系统的存储过程数量过百,建议规划一个自动化改写模板,然后人工复核。完全靠人工逐个改,效率太低也容易引入新 bug。
4.3 字符集与排序规则问题
国产数据库的字符集默认情况通常是 UTF-8,但 MySQL 5.7 时代很多系统使用的是utf8mb4_general_ci或utf8mb4_unicode_ci排序规则。
迁移时要注意几点:
- 源库连接串中的字符集参数(characterEncoding)必须保持统一,否则中文会乱码
- 排序规则不同可能导致 order by 结果不一致,尤其涉及中文字段排序时
- 表字段的 COLLATE 属性要跟着迁移走,不能只迁移数据
我遇到的一个实际案例:有一张字典表,字段排序用的是utf8mb4_general_ci,迁移到 PG 内核国产库后,默认的 UTF-8 排序规则变成了按 Unicode 码点排序,导致结果集顺序和原来不一样。这个 bug 查了半天才定位到,因为开发和测试环境数据量小,顺序差异不明显,一上生产数据,分页和下拉框的顺序全乱了。
5. 高可用与运维体系适配
5.1 高可用架构调整
MySQL 时代的高可用方案相对成熟,常见有 MHA、Orchestrator、Keepalived + VIP 等方式。到了国产数据库,很多方案不能直接套用。
我这次的实践里,高可用主要根据数据库类型分别处理:
| 原方案 | 国产库替代方案 | 备注 |
|---|---|---|
| MySQL Master-Slave + MHA | 官方高可用组件 / 主备自动切换工具 | 部分产品已提供类似组复制功能 |
| VIP 漂移 | 新库自带连接管理组件 | 需重新设计应用连接方式 |
| 读写分离中间件(MyCat / ShardingSphere) | 国产中间件或自研路由 | 兼容性取决于对 MySQL 协议的仿真程度 |
这里有个非常重要的工作:切换前要做高可用演练。不要等故障发生了才第一次测切换,否则在切换那一刻极易操作失误。我的做法是在迁移完成后,主动杀掉主库进程,观察从库能不能在预定时间内自动拉起,并记录业务受影响的时间窗口。
实际演练结果发现,某款国产库的自动切换需要外部健康检查程序参与,不能完全依赖数据库自身。于是我们在监控平台上额外加了一个切换触发脚本,一旦检测到主库状态异常,立即通过 API 调用高可用组件执行切换。
5.2 监控与告警适配
监控层面,原来用的 Prometheus + mysqld_exporter 监控 MySQL 指标,这套可以直接对接部分国产库,因为兼容 MySQL 协议的数据库依然能暴露兼容格式的指标。但对 PG 内核的库来说,就要换适配器,或者基于原生指标自己抓取。
具体落地时,我建议关注这几个监控项:
- 连接数(当前连接数、最大连接数、活跃连接数)
- 慢查询数量和耗时分布
- 主从同步延迟(单位秒,这个一定要在切换演练中验证)
- Buffer Pool 命中率
- 事务提交耗时
刚开始迁移的时候容易忽略“主从同步延迟”这个指标,但其实最危险的也是它。曾经有一次,业务大促流量上来后,从库延迟从几毫秒飙到几十秒,虽然业务没报错,但报表查询大量超时。监控告警如果没配到位,这种问题无法立刻感知。
5.3 备份恢复策略
国产数据库的备份工具和 MySQL 的 MySQLdump + binlog 增量备份体系并不完全一致。这里给几个建议:
- 优先使用数据库自带的备份工具,而不是沿用旧的 mysqldump 脚本
- 全量备份 + 归档日志备份组合必不可少
- 至少做一次完整的恢复演练,不能只验证备份文件生成成功
一个特殊情况:某个 PG 内核国产库的物理备份默认使用pg_basebackup,而这个工具在备份路径含中文字符时会报错。这类细节点很多,不实际演练完全发现不了。
6. 常见问题与排错经验
6.1 SQL 执行报错的排查思路
在国产化迁移过程中,最常遇到的场景就是“同样的 SQL 在 MySQL 能跑,在国产库报语法错误”。排查这类问题,我的思路是这样的:
- 先拿到完整报错信息和出错的 SQL
- 定位是语法不兼容还是语义不一致
- 到官方文档或社区查兼容性说明,重点看类型转换、函数映射部分
- 在测试实例上逐步简化 SQL,缩小到具体是哪个函数或者关键字导致问题
- 改写后放到完整的业务流程中回归测试
这里分享一个快速定位技巧:有的国产库提供了“兼容模式”开关,可以在会话级别开启 MySQL 兼容语法解析,虽然不完全保证语义一致,但能帮 SQL 先跑起来。我实际使用时,开启后大部分基础 SELECT 语句能直接执行,只有复杂存储过程和特殊函数需要后续处理。
6.2 主从同步中断处理
增量同步链路中间断了,比如源库 binlog 被清理,或者目标库进程被重启,重新拉起同步后通常会报错。这时候的处理顺序:
- 先确认断点时间,从源库找到对应的 binlog 文件和 position
- 如果文件已经被清理,只能做一次增量补偿或重新全量迁移
- 如果只是同步进程异常退出,直接从最后一个提交位置继续同步
我踩过最深的坑是:同步工具本身进程还活着,但日志已经不再写入,表面一切正常,实际上数据早就不同步了。后来我给同步链路加了“心跳表”,每 10 秒往里面插入一条最新时间戳记录,如果目标库的心跳表时间落后超过 1 分钟,就触发告警。
6.3 性能回退问题
迁移后最普遍的现象是:同样的 SQL,MySQL 跑 50 毫秒,国产库要跑 2 秒。遇到这种情况,通查顺序按以下步骤走:
- 先确认执行计划是否走了正确的索引,特别关注隐式类型转换导致索引失效
- 检查统计信息是否更新,国产库通常需要手动执行 analyze 或 vacuum
- 检查数据库参数是否针对当前硬件调整过,比如 buffer_pool_size、并行度等
- 如果有相同 SQL 在测试环境很快、生产环境慢的现象,优先考虑连接参数和会话配置差异
实际处理中,一次最常见的问题就是:迁移后统计信息还没更新,优化器选择了全表扫描。执行一次 ANALYZE TABLE 后,查询耗时直接下降了 80%。这类问题不是数据库本身慢,而是迁移流程里缺少了“统计信息刷新”这一步。
7. 扩展思考与经验总结
7.1 这个项目还能往哪个方向扩展
数据库国产化适配做完了,不代表项目就此结束。在实际推进过程中可以发现,围绕“国产化”这个主题,还牵扯出一堆周边系统的适配工作:
- 数据同步链路可以延伸到大数据生态,比如把国产库数据同步到数据仓库、数据湖
- 运维平台可以从开源监控体系走向国产化监控体系
- 应用层的 ORM 框架需要针对新数据库的特性调整批量插入、分页策略
如果要规划后续版本,我建议把这套迁移方案沉淀成一整套自动化工具,交付给其他业务团队复用。包括迁移评估工具、SQL 兼容性检查工具、同步链路监控工具,这三块都是价值很高的资产。
7.2 我踩过最深的三个坑
第一个坑是低估了存储过程改造的工作量。项目初期盘点时,我们预估有 60 个存储过程,结果实际排查时发现关联的历史脚本和定时任务里隐藏了 40 多个隐式存储过程,总计超过 100 个。这类场景在迁移评估阶段一定要做得非常彻底,连日志里动态拼接的 SQL 都要翻出来。
第二个坑是字符集转换。源库是utf8mb4,目标库是UTF-8,表面上看都是多字节编码,但排序规则和特殊字符的存储语义还是有差异。尤其是 emoji 字符和某些生僻汉字,在部分国产库的默认字符集下会变成乱码或报错。
第三个坑是备份恢复方案。我们原本沿用 mysqldump + binlog 的备份思路,结果某国产库的目标库根本不支持源库的备份文件直接恢复,必须使用它自带的备份格式。这个是在真正做恢复演练时才发现的问题,差点导致备份方案推倒重来。
7.3 给后来者的一些实在建议
根据我这次项目的实际操作,如果让我重新来做一遍,我会在最初阶段就重视下面这几件事:
- 第一时间建立 SQL 兼容性基线,把所有核心 SQL 收集起来,在目标库上跑一个兼容性评估报告
- 提前培训团队,让开发、测试、运维都了解目标库的新特性,而不是当成“另一个 MySQL”来用
- 迁移过程中每天做一次全量数据比对,不要放到快切换时才做,否则问题会集中在最后爆发
尤其需要提醒的是,不要迷信任何数据库厂商的“100% 兼容 MySQL”承诺。兼容性意味着大多数常用 SQL 可以直接用,但“大多数”绝对不等于“全部”。数据库替换本质上是一个风险工程,预期管理做得好,项目才顺利。