4、兼容性评估:MySQL/PG 协议兼容性、SQL 方言差异、存储过程与触发器迁移

好,咱们进入第四讲。这一讲我打算聊聊兼容性评估。说实话,这是云原生数据库迁移中最容易翻车的地方。你想想看,业务跑得好好的,一迁移过去,SQL 报错、存储过程跑不起来、触发器失效……那画面太美我不敢看。

我个人习惯,在做任何迁移之前,先花一周时间做兼容性摸底。别急着切流量,先把坑踩一遍,心里才有底。

4.1 MySQL/PG 协议兼容性

先说协议层。云原生数据库为了兼容生态,通常会支持 MySQL 或 PostgreSQL 的通信协议。但「支持」和「完全兼容」是两码事。

MySQL 协议兼容性

我遇到过不少号称兼容 MySQL 的数据库,比如 TiDB、OceanBase、PolarDB-X。它们都能听懂 MySQL 的握手协议、查询协议、预处理语句。但细节上会有差异:

  • 字符集:有些数据库只支持 utf8mb4,不支持 latin1。你原来的表如果用了 latin1,迁移后可能乱码。
  • 认证插件:MySQL 8.0 默认用 caching_sha2_password,老应用可能只支持 mysql_native_password。我踩过这个坑,迁移后应用连不上库,排查了半天。
  • 系统变量:像 sql_mode、time_zone 这些,不同数据库的默认值可能不同。建议迁移前统一设置。

核心建议:不要只看「兼容 MySQL 协议」这几个字。拿你的应用实际连一下,跑一遍全链路测试。协议兼容只是入门,真正的坑在 SQL 层面。

PostgreSQL 协议兼容性

PG 协议这边,像 CockroachDB、YugabyteDB、Greenplum 都支持。但 PG 的生态更复杂:

  • 扩展机制:PG 有丰富的扩展(如 PostGIS、pg_stat_statements),但云原生数据库不一定支持所有扩展。我见过有人迁移后才发现 PostGIS 用不了,只能回退。
  • 数据类型:PG 的数组、JSONB、枚举类型,有些数据库支持得不完整。比如 JSONB 的索引方式可能不同。
  • 事务隔离级别:PG 默认是读已提交,但有些云原生数据库默认是可重复读。这会导致业务逻辑出问题。

我的经验:迁移前先列一个清单,把你应用中用到的所有 PG 特性都写下来。然后对照目标数据库的文档,一个一个打勾。别偷懒,这一步省不了。

4.2 SQL 方言差异

SQL 方言差异,说白了就是同一个 SQL 写法,在不同数据库里可能含义不同,甚至直接报错。这是迁移中最头疼的部分。

常见差异点

差异项 MySQL 写法 PG 写法 云原生数据库常见行为
分页 LIMIT 10 OFFSET 20 LIMIT 10 OFFSET 20 大部分兼容,但某些数据库不支持 OFFSET 单独使用
字符串拼接 CONCAT(a, b)a || b a || b 有些数据库只支持 CONCAT,不支持 ||
日期函数 NOW(), CURDATE() CURRENT_TIMESTAMP, CURRENT_DATE 函数名不同,需要手动替换
空值排序 NULL 默认排在最前 NULL 默认排在最后 需要显式指定 NULLS FIRSTNULLS LAST
自增列 AUTO_INCREMENT SERIALIDENTITY 语法不同,DDL 需要改写

如何应对?

我建议分三步走:

  1. 静态扫描:用工具(如 SQLFluff、Sqitch)扫描所有 SQL 文件,找出不兼容的语法。
  2. 动态测试:把业务 SQL 在目标数据库上跑一遍,看有没有报错。我习惯写一个自动化脚本,逐条执行并记录结果。
  3. 人工审查:对于复杂的 SQL(比如多层嵌套子查询、窗口函数),人工过一遍最稳妥。机器有时候会漏掉逻辑差异。

注意:有些差异不会报错,但结果不同。比如 NULL 排序、字符串比较的 collation 规则。这种「静默错误」最危险,一定要用数据对比来验证。

4.3 存储过程与触发器迁移

存储过程和触发器,嗯,这里要注意。它们是迁移中的「硬骨头」。为什么?因为不同数据库的存储过程语言完全不同。

存储过程迁移

MySQL 用 SQL/PSM(类似 PL/SQL),PG 用 PL/pgSQL。语法差异很大:

  • 变量声明:MySQL 用 DECLARE,PG 也用 DECLARE,但位置和语法有细微差别。
  • 游标:MySQL 的游标语法和 PG 不同,需要改写。
  • 异常处理:MySQL 用 DECLARE ... HANDLER,PG 用 EXCEPTION 块。逻辑要重新组织。
  • 动态 SQL:MySQL 用 PREPAREEXECUTE,PG 用 EXECUTE ... USING

我遇到过最头疼的情况:一个存储过程有 500 行,里面用了大量 MySQL 特有的函数和游标。迁移时几乎重写了一遍。所以我的建议是:

能不用存储过程就不用。把业务逻辑放到应用层,数据库只做数据存储和查询。这样迁移成本最低。如果实在要用,那就做好重写的心理准备。

触发器迁移

触发器相对简单一些,但也要注意:

  • 触发时机:MySQL 支持 BEFOREAFTER,PG 还支持 INSTEAD OF(用于视图)。
  • 触发事件:MySQL 的 INSERTUPDATEDELETE 和 PG 一致,但 PG 还支持 TRUNCATE
  • 新旧行引用:MySQL 用 NEWOLD,PG 也用 NEWOLD,但语法略有不同。

我曾经迁移过一个系统,里面有 20 多个触发器,用来做数据审计。迁移到目标数据库后,发现触发器执行顺序和原来不同,导致审计数据错乱。排查了两天才找到原因。

避坑指南:迁移触发器时,一定要写单元测试。每个触发器单独测试,验证输入输出是否一致。别偷懒,否则上线后出问题,你连觉都睡不好。

4.4 兼容性评估工具与流程

最后,我分享一下我常用的兼容性评估流程:

  1. 导出 DDL:从源数据库导出所有表结构、索引、约束、存储过程、触发器。
  2. 自动转换:用工具(如 AWS DMS Schema Conversion、Oracle SQL Developer)尝试自动转换。
  3. 人工修正:工具转换不了的,手动改写。我一般会列一个「待办清单」,逐条处理。
  4. 功能测试:在目标数据库上创建对象,跑一遍功能测试用例。
  5. 性能测试:同样的 SQL,在源库和目标库上对比执行计划,看有没有性能退化。
  6. 数据对比:迁移一部分数据,用工具(如 pt-table-checksum)对比源库和目标库的数据一致性。

嗯,这套流程我用了很多年,基本没出过大问题。你想想看,迁移就像搬家,提前把东西分类打包好,到了新家才能快速归位。兼容性评估就是这个「打包」的过程。

下一讲,我们会聊数据迁移策略。到时候我会讲讲全量迁移、增量同步、还有双写方案。咱们到时候见。