ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL迁移openGauss实战:那些让你“迁移即翻车”的不兼容点

MySQL迁移openGauss实战:那些让你“迁移即翻车”的不兼容点 最近在评估一个从 MySQL 迁移到 openGauss 的项目开始看到“兼容 MySQL”的宣传以为建个库导个数据就能跑结果第一张建表脚本就报错。翻文档、查示例、做对照测试踩了一堆坑之后我整理了一份不兼容点对比表这里把实际遇到的和查证过的差异展开说一下。这篇文章不是劝退而是给准备做迁移的人一个清单减少“迁移即翻车”的概率。1. 兼容前缀背后的设计分叉openGauss并不想成为“另一个MySQL”1.1 openGauss的血统决定了兼容性边界openGauss的内核脱胎于 PostgreSQL 体系但做了大量自研改造和 MySQL 的兼容是“方言兼容”而不是“内核同构”。MySQL 以 InnoDB 为核心支持插件式存储引擎openGauss 的存储、事务、日志机制延续的是 PostgreSQL 那一套又加了自己的行存、列存、资源池化等能力。也就是说兼容层做在语法解析、系统视图、JDBC 协议等位置而不是把 InnoDB 的行为模仿一遍。这个定位带来的直接后果是常用的 SELECT、INSERT、UPDATE、DELETE 大多能跑但一旦触碰到 MySQL 特有语法、特殊函数、隐式规则行为立刻分叉。比如SHOW CREATE TABLE、information_schema的一系列查询在 openGauss 里看起来有但返回字段和 MySQL 不同很多 DBA 脚本迁移后直接失效。1.2 兼容模式不是万能开关openGauss 创建数据库时可以指定兼容模式比如DBCOMPATIBILITYBB 兼容 MySQL或AA 兼容 O 厂商。在 B 模式下很多 MySQL 语法会被识别例如AUTO_INCREMENT、IFNULL、DATE_FORMAT等。但“开启兼容模式”不等于“全兼容”官方文档里明确列了大量不兼容项而且不同版本的兼容程度不一样。如果创建数据库时用了默认模式再执行 MySQL 的建表脚本大概率是语法报错。即使创建了 B 模式库也可能只是把“语法错误”变成了“类型不匹配”或“行为不同”。所以第一个建议永远是先确认你的 openGauss 是什么版本、目标库是不是 B 兼容库再谈迁移。1.3 先看结论哪些系统适合迁移根据我这次评估的经验逻辑简单、以单表 CRUD 为主、没有复杂存储过程、不依赖 MySQL 特殊函数的应用迁移成本相对可控但牵扯到批量 UPDATE、触发器、自定义函数、复杂权限体系、特殊排序规则的一定要逐条核对。不兼容点对比表存在的意义不是劝你别迁而是让你在写改造方案前把所有坑提前标出来。2. 建表阶段就卡壳数据类型、默认值和时间精度差异2.1 数值类型与自增列的映射MySQL 建表非常喜欢TINYINT(4)、INT(11)这种带显示宽度的写法。openGauss 在 B 兼容模式下有 TINYINT 类型但 MySQL 的显示宽度会被忽略在非 B 模式下TINYINT 可能直接报错需要改成SMALLINT。更核心的是自增列差异。MySQL 用AUTO_INCREMENTopenGauss 在 B 模式下也支持AUTO_INCREMENT但底层实现是序列sequence。这带来几个实际影响重置自增值的方式不同。MySQL 用ALTER TABLE t AUTO_INCREMENT1000;openGauss 需要用setval(pg_get_serial_sequence(t, id), 1000, false);。复制表结构时MySQL 的CREATE TABLE t2 LIKE t1会保留自增属性openGauss 的LIKE语法需要额外加上INCLUDING DEFAULTS否则默认的nextval丢失。事务回滚后两者的自增 ID 都可能不回收但 openGauss 因为基于序列对setval的粒度控制比 MySQL 直接改表定义更灵活。2.2 字符串类型和默认值MySQL 里VARCHAR(255)的 255 通常指字符数openGauss 的VARCHAR(n)在多数场景也是字符数这个基本兼容。真正的坑在默认值上。MySQL 8.0 之前BLOB/TEXT不能有默认值openGauss 的限制更宽松但反过来MySQL 的TIMESTAMP可以写DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPopenGauss 在 B 模式下可能部分支持但为了稳妥我建议迁移时把ON UPDATE CURRENT_TIMESTAMP去掉应用层手动维护updated_at。不然哪天重启、切换驱动版本行为变了很难排查。另外一个隐藏差异是字符集。MySQL 的表常有DEFAULT CHARSETutf8mb4openGauss 没有这个属性字符集更多是集群级或库级配置。迁移时如果不处理中文乱码或字符集校验问题会集中爆发。2.3 日期时间与零值问题MySQL 的DATETIME允许0000-00-00 00:00:00这个零值老业务里甚至有人拿它当“空值”用。openGauss 严格拒绝这种零日期导入时直接报错。这是迁移数据时最经典的一类不兼容必须在导出前做数据清洗把零值改成NULL或合法日期。时区差异也要注意。MySQL 的TIMESTAMP内部存 UTC展示时按会话时区转换openGauss 的timestamp without time zone不关心时区带时区类型是timestamptz。如果原来的代码依赖 MySQL 的时区转换行为迁移后同一列存下来的时间可能在不同地区产生偏差。2.4 一个典型建表语句的对照拿最简单的用户表来说-- MySQL 写法 CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(64) NOT NULL DEFAULT , status TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4;在 openGauss B 模式下大致的等价写法是CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(64) NOT NULL DEFAULT , status INT NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) );看起来差别不大但ENGINE和CHARSET必须删掉TINYINT可能需要调整。直接把 MySQL 脚本扔过去跑一般会卡在第一个不认识的属性上。3. 偷偷改语法的后果UPDATE多表、DELETE别名与LIMIT行为对比3.1 UPDATE 多表关联的写法完全不同MySQL 常见的多表更新是这样UPDATE t1 JOIN t2 ON t1.id t2.tid SET t1.a t2.b WHERE t2.status 1;openGauss 走的是 PostgreSQL 风格的UPDATE ... FROMUPDATE t1 SET a t2.b FROM t2 WHERE t1.id t2.tid AND t2.status 1;表面只是语法调整但有个隐藏差异MySQL 的 JOIN UPDATE 在关联到多行时更新结果是不确定的官方文档也提醒不要依赖这种行为openGauss 用FROM子句时虽然通常也会选取某一行但行为由执行计划决定同样存在不确定性。迁移时不能只做语法翻译还要检查业务是否依赖“更新哪一行”的默认行为否则线上数据会变更出不同结果。3.2 DELETE 的别名与多表写法MySQL 支持DELETE a FROM t_user a JOIN t_order b ON ...这样给表起别名删除。openGauss 的等价写法是DELETE FROM t_user a USING t_order b WHERE a.id b.uid AND b.create_time 2020-01-01;注意openGauss 对 DELETE 别名的支持在不同版本有差异B 兼容模式下可能能识别 MySQL 风格也可能报错。如果团队里有同学习惯了 MySQL 多表删除迁移后第一反应是“不是兼容 MySQL 吗为什么这里不行”这个现象非常普遍。3.3 UPDATE/DELETE 与 LIMIT 组合MySQL 允许UPDATE t_user SET status 1 WHERE status 0 LIMIT 100;用来分批更新。openGauss 不支持 UPDATE/DELETE 带 LIMIT需要改写UPDATE t_user SET status 1 WHERE id IN ( SELECT id FROM t_user WHERE status 0 LIMIT 100 );但这个改写有坑子查询里的LIMIT如果没有ORDER BY所选行不可预期而且 openGauss 对UPDATE目标表出现在子查询中有限制可能直接报“UPDATE/DELETE cannot refer to the target table”。需要先SELECT id到临时表再关联更新。这个改写过程比想象中麻烦尤其在大表分批场景下执行计划可能完全不是预期。3.4 SELECT 的 LIMIT 偏移、排序兼容情况SELECT 的LIMIT 10, 20在 openGauss B 模式下能识别也支持LIMIT 20 OFFSET 10。但要注意两个数据库在“不写 ORDER BY 时的返回顺序”上几乎必然不同。MySQL InnoDB 按主键聚簇openGauss 是堆表结构物理扫描顺序不一样。分页接口如果依赖隐式顺序迁移后可能出现重复或缺失数据。3.5 REPLACE INTO 与 INSERT ON CONFLICTMySQL 的REPLACE INTO很常用openGauss B 模式部分支持但REPLACE本质是 delete insert副作用很大会触发删除/插入类触发器、更新自增 ID、对外键不友好。openGauss 更推荐INSERT ... ON CONFLICT语法例如INSERT INTO t (id, name) VALUES (1, a) ON CONFLICT (id) DO UPDATE SET name EXCLUDED.name;MySQL 的ON DUPLICATE KEY UPDATE可以不指定冲突列碰到任意唯一键冲突就更新openGauss 的ON CONFLICT必须指定唯一键或唯一约束。应用层原本一句 SQL 搞定的事务逻辑迁移后可能要改成先查后写或拆分多条 SQL。4. 存储过程、触发器和自增列让老应用“迁移即翻车”的重灾区4.1 存储过程语言和游标都不一样MySQL 存储过程更接近 SQL Server 风格用DELIMITER定义结束符循环用WHILE ... DO ... END WHILE异常处理用DECLARE CONTINUE HANDLER FOR NOT FOUND。openGauss 的主要存储过程语言是 PL/pgSQL用BEGIN ... END、LOOP、FOR、IF ... THEN ... END IF。一个典型差异是游标MySQLDECLARE cur CURSOR FOR SELECT ...; OPEN cur; FETCH cur INTO var;openGauss可以使用FOR row IN SELECT ... LOOP ... END LOOP绕开显式游标但如果要照搬 MySQL 游标语法差异很大。动态 SQL 也是重灾区。MySQL 用PREPARE ... FROM ... EXECUTEopenGauss 用EXECUTE IMMEDIATE或USING参数。如果原来的存储过程里动态拼了 SQL迁移基本等于重写。我这次评估一个订单模块光三个存储过程就花了两天时间改写还不敢保证所有边界条件一致。4.2 触发器FOR EACH ROW 的默认行为MySQL 的触发器必须写FOR EACH ROWopenGauss 同时支持行级和语句级触发器。迁移时表面上把CREATE TRIGGER改一改即可但触发器内部的赋值语法有差异MySQLSET NEW.col value;openGaussNEW.col : value;还有触发器递归行为、嵌套层级上限两边默认值不同。如果原业务对触发器有较强的依赖迁移后一定要做并发场景下的触发器测试否则一个简单的 UPDATE 可能触发连锁更新甚至死锁。4.3 自增列AUTO_INCREMENT、SERIAL 与 IDENTITY 的体验差异openGauss 的AUTO_INCREMENT在 B 模式底层是序列会生成一个隐式序列。这导致MySQL 用ALTER TABLE t AUTO_INCREMENT1000重置openGauss 要用setval。SHOW CREATE TABLE时openGauss 不会像 MySQL 那样直观显示AUTO_INCREMENT1000而是显示DEFAULT nextval(table_id_seq)。用CREATE TABLE t2 (LIKE t1 INCLUDING DEFAULTS)自增默认值才会带过来如果漏了INCLUDING DEFAULTS新表的自增列就变成普通 INT 了。这些问题不会在建表时报错而是在数据插入后才暴露比较隐蔽。4.4 常用函数差异IFNULL、GROUP_CONCAT、DATE_FORMAT函数层面是最容易批量报错的。拿几个高频函数来说IFNULL(a, b)MySQL 非常常用openGauss A 模式下不存在需要改成COALESCE(a, b)B 模式虽然支持但从长期维护角度看统一用COALESCE更省心。GROUP_CONCATMySQL 里做行转列非常方便openGauss 对应的函数是STRING_AGG或LISTAGG但去重语法、排序语法都不一样。DATE_FORMATMySQL 的%Y-%m-%d %H:%i:%s格式符在 openGauss B 模式有支持但官方文档也提示部分格式符行为有差异如果不小心用了 PG 风格的to_char又是一种写法。建议迁移前用脚本扫描代码和存储过程中的这些函数列一个替换清单不要等运行期一个一个爆。5. 事务隔离、MVCC和锁行为并发下最隐蔽的不兼容5.1 默认隔离级别与可重复读的差异MySQL InnoDB 默认隔离级别是REPEATABLE READopenGauss 默认是READ COMMITTED。这个差异影响非常大如果应用在 MySQL 下依赖“事务内多次 SELECT 结果一致”迁移到 openGauss 后必须显式设置SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;或者按照新库重写事务边界。openGauss 也支持REPEATABLE READ但实现机制和 InnoDB 不同。InnoDB 的快照在事务第一次读时建立openGauss 的快照机制继承 PostgreSQL 风格对同一行并发更新的处理逻辑不同。5.2 UPDATE 冲突与死锁差异并发更新同一行时MySQL InnoDB 默认等待行锁直到innodb_lock_wait_timeout超时然后报锁等待超时错误。openGauss 在READ COMMITTED下UPDATE 等待行锁释放后会重新评估条件可能更新零行在REPEATABLE READ下如果发现行已被其他事务修改可能直接报“could not serialize access due to concurrent update”。这个行为差异很难在功能测试阶段发现需要在压测阶段用高并发场景去验证。我这次就遇到一个订单状态更新接口MySQL 下一直正常openGauss 下偶发异常排查后发现是隔离级别下的冲突处理策略不同。5.3 锁粒度与锁等待监控MySQL DBA 习惯用SHOW PROCESSLIST和performance_schema.data_lock_waits查锁阻塞链openGauss 对应的是pg_locks视图和pg_stat_activity字段命名、等待事件类型完全不同。之前的排障脚本不能直接搬。锁表问题在 MySQL 里通常指 DML 行锁堆积openGauss 里还要注意 DDL 的ACCESS EXCLUSIVE锁。部分ALTER TABLE操作在 MySQL 8.0 可以在线执行openGauss 的某些 DDL 会阻塞读写。如果业务有凌晨跑 DDL 的习惯迁移后要重新验证这些窗口是否还能用。5.4 隐式提交与 DDL 的事务性MySQL 的 DDL 会隐式提交执行CREATE INDEX或ALTER TABLE后不能回滚。openGauss 支持事务性 DDLCREATE TABLE、ALTER TABLE可以包在事务里执行错了可以ROLLBACK。这个差异对迁移脚本的影响是MySQL 习惯把 DDL 和 DML 分开执行openGauss 里 DDL 和 DML 可以混在一个事务里但这也意味着一个长事务会持有更多锁需要更精细地控制事务边界。6. 一张不兼容点速查表和一个理性的迁移建议6.1 核心对比表以下是根据我的实测和官方文档整理的核心差异同一版本之间可能还有微调建议以你实际安装的版本为准。维度MySQLopenGauss迁移注意事项默认端口33065432连接串、防火墙、容器映射都要改默认隔离级别REPEATABLE READREAD COMMITTED依赖 RR 的应用要显式设置自增列AUTO_INCREMENTB 模式支持 AUTO_INCREMENT底层序列重置方式、LIKE 复制表结构都要注意多表 UPDATEUPDATE t1 JOIN t2 SET ...UPDATE t1 SET ... FROM t2语法改写且要检查多行关联的不确定性DELETE 带 LIMIT支持不支持改子查询或临时表REPLACE INTO支持有限支持推荐 ON CONFLICT重写 SQL注意触发器副作用ON DUPLICATE KEY UPDATE支持用 ON CONFLICT 指定冲突列唯一键处理逻辑不同GROUP_CONCAT支持STRING_AGG / LISTAGG排序、去重语法要重写IFNULL支持A 模式用 COALESCE建议统一用 COALESCEDATE_FORMAT支持B 模式部分支持格式符有差异需要回归验证零日期支持 0000-00-00不支持导入前必须清洗表存储引擎ENGINEInnoDB 等没有 MySQL 插件式引擎建表脚本要去掉 ENGINE 属性字符集utf8mb4 等数据库/集群级配置注意乱码和排序规则存储过程MySQL 风格PL/pgSQL 为主工作量最大的改写点触发器FOR EACH ROW 必填行级/语句级赋值语法和递归行为不同事务性 DDL隐式提交支持事务内回滚长事务和锁窗口重新评估备份工具mysqldumpgs_dump参数、输出格式完全不同JDBC 驱动mysql-connector-jopenGauss JDBC连接 URL、SSL 参数名不同6.2 迁移前必须做的几件事我这次的实操步骤大致如下供参考搭一个 openGauss 实例创建 B 兼容模式数据库把原 MySQL 建表脚本整体导入一遍记录所有报错。扫描应用代码和 SQL 脚本找出LIMIT在 UPDATE/DELETE 中使用、REPLACE INTO、ON DUPLICATE KEY UPDATE、GROUP_CONCAT、DATE_FORMAT、IFNULL等关键词单独整理成改造清单。导出数据前先做数据校验重点看零日期、字符集、超长字符串。清洗逻辑可以在导出 SQL 里做也可以在同步工具里做。应用层改造连接串、驱动、JDBC 参数。openGauss 的连接串格式和 SSL 参数和 MySQL 不一样像是useSSL、sslmode这类参数直接套用会报错或产生误导。做并发压测重点观察死锁、锁等待、隔离级别冲突。如果原先用了REPEATABLE READ压测时一定要在READ COMMITTED和REPEATABLE READ两种模式下各跑一遍。灰度切换前做增量同步和双写对比不要直接全量替换。6.3 我的建议不用因为这份不兼容清单就觉得 openGauss 很弱。相反它在高可用、资源池化、某些分析场景上有自己的优势。但“迁移”不是简单的数据搬家而是一次完整的技术栈切换。最稳妥的做法是提前建一个兼容性测试环境把自己项目的建表脚本、存储过程、核心 SQL 全部跑一遍把报错和异常行为记录下来再决定改造工作量。这份对比表只是起点实际项目里一定还会遇到文档里没写到的差异。迁移团队里最好有一个人专门负责整理“我们项目自己的不兼容点清单”这个清单比任何官方兼容性说明都更能指导后续的落地改造。我个人的体会是把兼容模式建对、把自增列和日期类型提前清洗干净就能避免一半以上的迁移问题。
返回列表