
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-purgedOFF \ --master-data2 \ --routines \ --triggers \ --databases db_prod db_prod.sql参数解释--single-transaction基于 InnoDB 的事务快照保证导出过程中不锁表业务可以继续写入--set-gtid-purgedOFF如果目标库不需要 GTID 复制关掉避免导入报错--master-data2记录导出时刻的 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_INCREMENTPG 系数据库使用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 SQLSTATEPG 用EXCEPTION WHEN others THENMySQL 支持动态 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 性能回退问题迁移后最普遍的现象是同样的 SQLMySQL 跑 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 可以直接用但“大多数”绝对不等于“全部”。数据库替换本质上是一个风险工程预期管理做得好项目才顺利。