1. 项目背景:为什么要把MySQL迁到OceanBase
最近在负责一套业务系统的数据库迁移,源端是跑了快六年的MySQL 8.0.28,目标端是OceanBase 4.x社区版。业务方给的理由很直接:单机MySQL的连接数快到上限,高峰期CPU经常飙到80%以上,日志表和流水表越滚越大,DBA半夜起来清数据已经清出心理阴影了。OceanBase在扩展性、高可用和分布式事务上的能力确实更合适,再加上它兼容MySQL协议,应用层改动成本相对低,于是立项做了迁移。
这里要澄清一个常见的误解:很多人以为“兼容MySQL协议”等于“MySQL的建表语句能直接跑”,实际上远没那么简单。OceanBase的MySQL模式虽然语法上做了大量兼容,但底层毕竟是分布式架构,很多MySQL的隐式规则、默认行为、字符集实现细节并不完全一致。我这次就是在“结构迁移”这一步卡了整整两天,建表脚本一执行就报错,报错信息五花八门,网上能搜到的案例又少,最后只能自己对着报错一条条啃。
这篇文章就是把我这次从MySQL到OceanBase结构迁移踩过的坑、排查思路和最终的解决方案完整记录下来,重点说清楚哪些地方容易失败、背后的原因是什么、怎么改才能顺利迁过去。如果你正准备做类似的数据库迁移,或者已经在迁移路上被DDL报错折磨,这篇内容应该能帮你少走不少弯路。
先说明一下这次迁移的基本盘:源库有200多张表,其中InnoDB引擎占绝大多数,少量MyISAM历史表,涉及字符集、自增列、分区表、外键约束、生成列、函数索引这些常见特性,数据量最大的表超过2亿行,整体迁移后面还有全量数据同步和增量同步,但结构迁移是第一步,这一步过不去后面全是空谈。
2. 结构迁移失败的典型症状与根因定位
2.1 首轮迁移:直接mysqldump导DDL,结果惨不忍睹
我第一轮的做法很粗暴,直接用mysqldump只导出表结构,然后把SQL文件灌到OceanBase里执行。命令大概是这样的:
mysqldump -h源库IP -P3306 -u迁移账号 -p'密码' \ --no-data --skip-comments --skip-add-drop-table \ --set-gtid-purged=OFF 业务库名 > schema.sql然后登录OceanBase,用source命令或者通过DBeaver直接执行这个SQL文件。结果第一张带utf8mb4_0900_ai_ci排序规则的表就执行失败了,报错内容大致是:
ERROR 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'这个错误很典型,也很好理解:MySQL 8.0默认字符集是utf8mb4,默认排序规则是utf8mb4_0900_ai_ci,但OceanBase 4.x的MySQL模式对排序规则的支持列表里并没有0900这个版本。实际上OceanBase支持的是utf8mb4_general_ci、utf8mb4_bin这些更早期的排序规则。这个坑基本上所有从MySQL 8.0往OceanBase迁的人都会遇到,属于首轮踩坑的“开胃菜”。
接着往下执行,又陆续冒出来一批报错,我简单归类了一下,主要有这几类:
- 排序规则不支持,就是上面说的utf8mb4_0900_ai_ci
- 建表语句里的
ENGINE=InnoDB没被识别(OceanBase其实可以忽略这个关键字,但某些版本会直接报语法错误) AUTO_INCREMENT=12345这种自增种子值在OceanBase里被拒绝ON UPDATE CURRENT_TIMESTAMP在某些特殊组合下不生效- 外键约束依赖的表还没创建,导致顺序依赖失败
- 分区表的分区表达式语法不兼容
JSON列的默认值写法不被接受
看到这么多问题,我意识到不能指望一个SQL文件从头灌到尾,必须分步骤、分模块地处理。
2.2 定位思路:不要瞎猜,用最小化复现逐个击破
遇到批量失败的时候,最容易犯的错误是“看着报错改一处,再跑一遍,又报新的错,再改,陷入死循环”。我的建议是先把所有报错收集起来,分类归纳,然后针对每一类写一个最小化的DDL语句去单独测试,确认OceanBase到底支持什么、不支持什么。
比如我先把所有报错SQL拉到本地文件里,用grep把报错行附近的内容截出来,统计一下高频错误:
grep -n "ERROR" 导入日志.txt | awk -F: '{print $3}' | sort | uniq -c | sort -rn这样能快速看到错误分布,哪些是高频问题、哪些是个例。我当时的统计结果是排序规则不支持占了将近一半,其它分布比较散。这个操作看起来很简单,但很管用,能避免你眉毛胡子一把抓。
更关键的思路是:先确保有问题的表单独跑通,再全量导入。我通常的做法是把DDL文件按表拆开来,手工挑出那些报错的表,逐个执行、逐个修改,全部通过后再重新合并成一个完整的schema脚本。合并前顺手把不兼容的关键字用sed批量替换掉,效率会高很多。
3. 核心差异点详解:MySQL与OceanBase在结构上的兼容性差距
3.1 字符集与排序规则:最容易被忽略的“第一颗雷”
这次迁移最大的失败原因就是字符集排序规则不兼容,所以我把这个放在最前面说。
MySQL 8.0默认的utf8mb4_0900_ai_ci在OceanBase里不认识,OceanBase 4.x的MySQL模式支持以下这些排序规则:
| 字符集 | 支持的排序规则 |
|---|---|
| utf8mb4 | utf8mb4_general_ci(默认)、utf8mb4_bin、utf8mb4_unicode_ci |
| utf8 | utf8_general_ci、utf8_bin |
| gbk | gbk_chinese_ci、gbk_bin |
| latin1 | latin1_swedish_ci、latin1_bin |
对照一下就明白了,0900_ai_ci是MySQL 8.0新引入的Unicode 9.0排序实现,OceanBase的兼容实现没有跟进到这个粒度。所以建表语句里只要写了CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,大概率就报错。
解决方案有两个方向:
方案A:全局替换排序规则。把DDL里所有的utf8mb4_0900_ai_ci替换成utf8mb4_general_ci。这个方法适合大多数业务场景,因为general_ci按字节比较,性能也很稳定,业务上如果没用到特别复杂的Unicode排序规则,完全够用。
sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' schema.sql方案B:按列指定_utf8mb4_bin。如果业务对大小写敏感或者有特定排序需求,可以在具体字段上单独指定utf8mb4_bin,这样既能保留大小写敏感性,也不会触发不兼容报错。比如用户名、订单号这类需要精确匹配的字段,用_bin反而更合适。
注意一点:替换完排序规则之后,一定要在目标库执行一段校验SQL,看看有没有漏网的。可以用information_schema查一下:
SELECT table_schema, table_name, column_name, collation_name FROM information_schema.columns WHERE table_schema = '业务库名' AND collation_name NOT IN ('utf8mb4_general_ci', 'utf8mb4_bin');顺带说一句,很多人在MySQL里查询时遇到过“大小写不敏感”的困惑,比如WHERE name = 'abc'能查到ABC,这个行为本质上就是排序规则决定的。_ci结尾的规则不区分大小写,_bin结尾的规则区分大小写。OceanBase的MySQL模式也遵循同样的逻辑,所以迁移后如果发现查询行为有变化,优先检查排序规则的差异。
3.2 数据类型映射:这些类型到了OceanBase会“水土不服”
MySQL的数据类型和OceanBase大部分是兼容的,比如INT、BIGINT、VARCHAR、DECIMAL、DATETIME这些直接用没问题,但有几个类型需要特别留意。
JSON类型:MySQL 8.0里JSON是原生类型,OceanBase 4.x也支持JSON类型,功能上大体兼容,但有一个细节:MySQL 8.0里JSON列不允许有默认值(除非是DEFAULT (json_expr)这种表达式默认值,8.0.13之后支持),OceanBase对JSON列的默认值支持也有类似的限制。所以建表时如果写了json_col JSON DEFAULT NULL,可能没问题,但如果写了json_col JSON DEFAULT '{}'这种,大概率会被拒。处理方式很简单:把JSON列的默认值去掉,由应用层写入时自行处理。
ENUM和SET类型:MySQL里用得很爽,但OceanBase的兼容性有限。我实测发现OceanBase支持ENUM类型,但底层存储逻辑和MySQL不太一样,尤其是排序和比较的规则可能有细微差异。SET类型在OceanBase里的支持更弱,建议迁移前就把ENUM和SET改成VARCHAR + CHECK约束,或者直接用TINYINT存枚举值,由应用层转换。这次迁移我直接把两张表的SET字段改成了VARCHAR,避免后续数据同步阶段出幺蛾子。
BOOLEAN类型:MySQL里BOOLEAN本质是TINYINT(1),OceanBase的MySQL模式也这么实现,这点没问题。但要注意如果用BOOL关键字建表,OceanBase某些版本会解析成TINYINT,和MySQL行为一致,倒不需要特殊处理。
TIMESTAMP和DATETIME:MySQL 8.0里TIMESTAMP有2038年问题,DATETIME范围更大,OceanBase也类似。但有个坑:MySQL 8.0.18之后,explicit_defaults_for_timestamp参数默认开启,TIMESTAMP列不再自动加DEFAULT CURRENT_TIMESTAMP;OceanBase的默认行为可能不一致。建表时最好显式写出默认值,不要依赖数据库隐式行为。
DECIMAL精度:MySQL的DECIMAL最大精度65位,OceanBase也是65位,但这个精度在OB内部是转成变长编码存储的,如果业务上有超过30位的DECIMAL,建议测试一下运算精度是否完全一致,不要想当然。
我把这次迁移中整理的类型映射表放在这里,供参考:
| MySQL类型 | OceanBase兼容情况 | 建议 |
|---|---|---|
| INT/BIGINT/SMALLINT | 完全兼容 | 直接用 |
| VARCHAR/CHAR | 完全兼容 | 注意字符集和长度定义 |
| TEXT/MEDIUMTEXT/LONGTEXT | 兼容 | 建议迁移后验证最大长度和排序行为 |
| BLOB/ VARBINARY | 兼容 | 直接用 |
| JSON | 基本兼容 | 去掉默认值,谨慎使用表达式 |
| ENUM | 有限兼容 | 改VARCHAR + CHECK |
| SET | 兼容弱 | 改VARCHAR或INT |
| BOOLEAN/TINYINT(1) | 兼容 | 直接用 |
| TIMESTAMP/DATETIME | 兼容但默认值行为有差异 | 显式定义默认值 |
| DECIMAL/NUMERIC | 兼容 | 精度超过30位要额外验证 |
| GEOMETRY/空间类型 | 暂不支持 | 迁移前需重新设计 |
3.3 自增列与AUTO_INCREMENT:一个关键字引发的“血案”
MySQL建表时经常这么写:
CREATE TABLE t_order ( id BIGINT NOT NULL AUTO_INCREMENT, ... ) ENGINE=InnoDB AUTO_INCREMENT=10086 DEFAULT CHARSET=utf8mb4;这段SQL在MySQL里毫无问题,但到了OceanBase,AUTO_INCREMENT=10086这个表选项可能被直接忽略或者报错。我当时遇到的报错就是第二张带自增种子值的表挂掉了。
排查后搞清楚了两点:第一,OceanBase支持AUTO_INCREMENT列属性,但表级选项AUTO_INCREMENT=n的兼容性在不同版本上表现不一致,4.2之前的版本可能直接报语法错误;第二,OceanBase的自增列在分布式架构下实现的是全局自增,如果一张表有分区,自增列和分区键不能有冲突,否则会报错。
解决方案很直接:建表时去掉表级的AUTO_INCREMENT=n,让自增从1开始,或者等表建好后再用ALTER TABLE t_order AUTO_INCREMENT=10086;来设置。至于分区表的问题,就要看下面的分区章节了。
3.4 分区表与索引:差异最隐蔽的部分
这次迁移里最让我头疼的就是分区表。MySQL 8.0支持RANGE、LIST、HASH、KEY四种分区类型,还支持子分区。OceanBase也支持这些分区类型,但语法和语义上有不少坑。
坑一:RANGE分区表达式。MySQL允许这样写:
PARTITION BY RANGE (YEAR(create_time)) ( PARTITION p2021 VALUES LESS THAN (2022), PARTITION p2022 VALUES LESS THAN (2023) )OceanBase虽然支持YEAR()函数,但分区键的数据类型需要能隐式转换为整数类型,如果create_time是VARCHAR类型,OceanBase可能拒绝执行。我遇到的实际报错是ERROR 1235 (42000): This version of OceanBase doesn't support this feature,这个报错信息太笼统,只能靠猜。排查后建议直接用整数类型的列做分区键,或者在源端先把分区表达式改成TO_DAYS(create_time)这种明确返回整数的形式。
坑二:KEY分区。MySQL的KEY分区支持任意列,OceanBase对KEY分区的支持相对弱,尤其是多列KEY分区可能不支持。实测下来,把KEY分区改成HASH分区是最省事的方案,分区列不变,效果等价。
坑三:索引差异。MySQL里前缀索引很常见,比如KEY idx_name (name(20)),OceanBase对前缀索引的支持有限,某些版本会报错。遇到这种情况,要么去掉前缀长度,直接建全列索引(如果字段太长可能影响性能),要么改用生成列 + 普通索引的方式。另外MySQL 8.0支持函数索引,OceanBase的MySQL模式对函数索引的支持也有限,遇到就改写吧,别硬刚。
分区这块我强烈建议:能不分区就先不分区,等数据迁移完成后再手工补分区。这样能降低结构迁移的失败率,数据同步阶段如果分区键设置不当,还能有机会调整。当然如果业务表已经按分区维度做数据归档,那就必须迁移前就把分区定义好,这种情况建议先在OceanBase的测试租户里把分区DDL跑通,再走正式流程。
3.5 外键与依赖顺序:先建父表,再建子表,别偷懒
200多张表互相之间有不少外键关系。mysqldump导出的DDL文件默认按照表名字母顺序排列,这就会导致外键依赖的父表还没有创建,子表先执行,直接报错。
其实MySQL自身导出时会处理依赖顺序,但mysqldump在多表导出时生成的顺序不一定满足所有外键依赖链。我遇到的情况是,A表外键引用B表,B表外键引用C表,C表又引用A表,形成循环依赖,这种结构在MySQL里能建,但要在迁移时处理就得花点心思。
我的处理策略分三步:
- 先导出所有表结构,去掉外键约束部分,只建基础表和索引;
- 全部表建好后再单独执行外键约束的ALTER语句;
- 如果存在循环依赖,就选择一张表的约束最后添加,打破循环。
具体操作是把mysqldump导出的DDL里所有外键相关的行抽出来:
grep -n "FOREIGN KEY" schema.sql然后单独保存为外键脚本,在基础表全部创建完成后再执行。这样比直接依赖dump出来的顺序要可靠得多。
4. 实操:一套可复用的结构迁移修正方案
4.1 迁移前准备:把源库信息摸清楚
别上来就导出DDL,先花点时间把源库的结构信息摸清楚。我用的是information_schema,查表数量、字符集分布、引擎分布、分区表数量、外键数量、自增列数量,这样心里有数。
-- 表数量 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '业务库名'; -- 字符集分布 SELECT DEFAULT_CHARACTER_SET_NAME, COUNT(*) FROM information_schema.tables WHERE table_schema = '业务库名' GROUP BY DEFAULT_CHARACTER_SET_NAME; -- 分区表清单 SELECT table_name, partition_method, partition_expression FROM information_schema.partitions WHERE table_schema = '业务库名' AND partition_name IS NOT NULL; -- 外键清单 SELECT table_name, constraint_name, referenced_table_name FROM information_schema.key_column_usage WHERE table_schema = '业务库名' AND referenced_table_name IS NOT NULL;这些信息能帮你提前预判迁移风险,比如发现大量MyISAM表,就要提前想好转换方案;发现有空间类型字段,就要提前准备替代设计。
4.2 DDL批量改写:用脚本替代手工修改
面对200多张表的DDL,手工改肯定是不可行的。我写了一个简单的shell + sed组合脚本,把已知的不兼容片段批量替换掉。核心思路其实就是“先粗后细”:先用脚本处理掉90%的通用问题,剩下10%的特殊情况再逐个手工处理。
我的脚本大致长这样:
#!/bin/bash INPUT=$1 OUTPUT=$2 sed -e 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' \ -e 's/CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci/CHARACTER SET utf8mb4/g' \ -e 's/ENGINE=InnoDB//g' \ -e 's/ENGINE=MyISAM//g' \ -e 's/AUTO_INCREMENT=[0-9]*//g' \ -e 's/COLLATE=utf8mb4_0900_ai_ci//g' \ $INPUT > $OUTPUT注意最后一定要人工复核:grep -i "0900" $OUTPUT,确认没有遗漏。
如果需要更精细的控制,也可以用Python脚本逐条解析DDL,针对CREATE TABLE语句做更细粒度处理,但就这次迁移而言,sed已经完全够用了。工具不是越复杂越好,能解决问题就行。
4.3 分批迁移策略:先无依赖,再有依赖,分区最后
整体迁移顺序我建议按下面的优先级执行:
- 先迁移无外键依赖、无分区的普通表;
- 再迁移有外键依赖的表(父表优先);
- 分区表单独批量迁移;
- 全部表结构建好后再补外键约束;
- 最后统一处理索引、触发器、存储过程、视图等对象。
这个顺序的核心逻辑是:把约束依赖和性能风险分开处理。建表本身不涉及数据,速度很快,但外键和索引如果过早添加,后续某些表的DDL调整会被约束卡住,增加无谓的返工。
4.4 结构校验:迁移完不等于结束,要逐项核对
结构迁移完成后,一定要做一次系统的结构比对,别急着进入数据同步阶段。我的校验方法是写对比SQL,分别在MySQL和OceanBase上执行,然后把结果拿来做diff。
核对项包括:
- 表数量是否一致;
- 每张表的列数量、列名、数据类型、是否可空、默认值是否一致;
- 主键、唯一索引、普通索引的数量和字段组合是否一致;
- 分区表的数量、分区类型、分区边界是否一致;
- 字符集和排序规则是否一致;
- 外键数量是否一致。
在OceanBase上查information_schema的方式和MySQL基本一致。比如查所有表的列数量:
SELECT table_name, COUNT(*) AS column_count FROM information_schema.columns WHERE table_schema = '业务库名' GROUP BY table_name;然后两边导出结果,用diff命令对比:
mysql -h源库 -P3306 -u用户 -p密码 -e "SELECT ..." > mysql_columns.txt obclient -h目标库 -P2881 -u用户@租户#集群 -p密码 -D 业务库名 -e "SELECT ..." > ob_columns.txt diff mysql_columns.txt ob_columns.txt注意OceanBase的客户端是obclient,连接串格式和MySQL客户端不一样,很多第一次用的人容易栽在这上面。如果不想折腾命令行,用DBeaver也是可以的,DBeaver支持连接OceanBase(选择MySQL驱动然后改端口和URL即可),图形化界面看结构更直观。实测DBeaver连接OceanBase的配置方式:驱动选MySQL,主机填OB的IP,端口填2881(直连模式)或者2883(proxy模式),用户名填“用户名@租户名”,密码照常,URL里加上?useSSL=false避免证书问题。
4.5 常见问题速查表
下面这个表是我这次迁移过程中遇到的报错和对应的解决办法,基本涵盖了结构迁移最常见的坑:
| 报错信息 | 可能原因 | 解决办法 |
|---|---|---|
| Unknown collation: 'utf8mb4_0900_ai_ci' | 排序规则不兼容 | 全局替换为utf8mb4_general_ci |
| ERROR 1235: This version doesn't support this feature | 特性不支持,原因较泛 | 定位到具体DDL语句,拆解最小化复现 |
| ERROR 1064: You have an error in your SQL syntax | 语法不兼容 | 检查分区表达式、前缀索引、函数索引 |
| ERROR 1101: BLOB/TEXT column can't have a default value | TEXT/BLOB默认值问题 | 去掉默认值或改用VARCHAR |
| ERROR 1075: Incorrect table definition | 自增列未设为索引键 | 确保AUTO_INCREMENT列是主键或唯一索引 |
| ERROR 1215: Cannot add foreign key constraint | 外键约束类型或字符集不一致 | 检查两边字段类型和字符集是否完全一致 |
| ERROR 1564: This partition function is not allowed | 分区表达式不允许 | 改用整数列或TO_DAYS函数 |
| ERROR 1822: Failed to add the foreign key constraint | 缺索引导致外键无法创建 | 先在外键列建索引,再建外键 |
| ERROR 1904: Invalid partition name | 分区命名问题 | 检查分区名格式和长度 |
| ERROR 3105: Specified column is not in the partition | 分区键不在表中 | 修改分区键列 |
5. 一些更深层的排查思路与避坑技巧
5.1 报错信息只是入口,不等于答案
OceanBase很多报错信息写得非常宽泛,比如ERROR 1235只告诉你“这个版本不支持这个功能”,但具体是哪个功能不支持,完全没说。这时候如果盯着报错信息本身去搜,基本找不到答案。
正确的排查方式是把报错对应的DDL语句单独拎出来,做最小化复现:
- 先建一张只有核心字段的表,不加索引、不加分区、不加约束,看能不能通过;
- 逐步添加约束、索引、分区,每加一步执行一次,定位到具体是哪个子句导致报错;
- 拿到最小复现语句后再到官方文档里查对应特性的兼容性说明。
这样做效率最高,也最容易定位问题根源。
5.2 SQL模式对齐:一个容易忽略的全局变量
MySQL的sql_mode会影响很多建表行为,比如是否允许零日期、是否严格模式、NO_ZERO_DATE等。OceanBase也有类似的sql_mode变量。如果源端和目标的sql_mode不一致,同一个DDL在两边的表现就可能不同。
比如源库MySQL的sql_mode里没有NO_ZERO_DATE,那么建表时允许DEFAULT '0000-00-00',但OceanBase默认开启了严格模式,这个DDL就会失败。解决方案是在执行DDL前,把两边的sql_mode对齐:
-- 查看MySQL的sql_mode SELECT @@sql_mode; -- 在OceanBase会话里设置成同样的值 SET session sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION';千万不要小看这个变量,它可能就是你的DDL“时好时坏”的根本原因。
5.3 大小写敏感性:提前约定好策略
MySQL的lower_case_table_names参数决定了表名和库名的大小写是否敏感,这个参数在Mac和Linux上默认是0,在Windows上是1。OceanBase也有类似的配置。如果源端表名是大写混合的(比如OrderInfo),迁移到OceanBase后可能发生大小写处理不一致的情况。
我这次的源库是Linux环境,lower_case_table_names=0,OceanBase那边默认是区分大小写的,所以表名一致,没出问题。但如果你是从Windows环境迁过来,或者目标端配置有变,建议在迁移前先把表名统一成小写,避免后续应用层查询出现“table doesn't exist”这种玄学问题。
5.4 国密SM4加密带来的额外注意点
这次迁移还涉及一个特殊背景:OceanBase企业版支持国密SM4加密,其中表级加密、索引加密和日志加密分别有独立的加密属性。如果目标端开启了SM4加密,那么建表时可以使用ENCRYPTION = 'Y'选项来指定加密。结构迁移时要注意,源端MySQL的表是没有加密属性的,如果你直接建表,默认不加密;而业务如果有合规要求需要加密,就得在建表语句里显式加上加密子句,或者在迁移后统一执行ALTER TABLE ... ENCRYPTION='Y'。
这个点很多人会忽略,等数据写完才发现要加密,结果又得做一次在线DDL变更,白白增加工作量。建议在结构迁移阶段就根据业务表的数据级别,把需要加密的表统一加上加密属性。
5.5 关于“结构迁移”本身的定位
很多人觉得结构迁移只是数据迁移的附属品,随便跑通就行。但我这次的经验是:结构迁移是整个迁移链路里最需要抠细节的一步。原因很简单:
- 结构对了,全量数据同步才能顺利映射;
- 结构里包含字符集、排序规则、约束、索引,这些决定了后续增量同步和查询性能的底子;
- 结构迁移阶段发现的差异点,往往也是应用层SQL需要适配的点,早发现早修改,不会等到生产切换时才崩。
所以哪怕只是为了赶工期,也别跳过结构比对这一步。真实项目里,结构迁移花了三天时间,但换来的是数据同步阶段几乎没有因为结构问题返工,这笔账怎么算都是划算的。
最后说点实际感受
这次迁移踩坑踩到最后,我的体感是:MySQL和OceanBase之间,语法层面的兼容已经做得相当好了,真正容易翻车的全是“默认行为”的差异——默认字符集、默认排序规则、默认SQL模式、默认时间戳行为。这些东西MySQL里有自己的一套习惯,OceanBase有自己的实现边界,两条线不重叠的部分,就是迁移报错的集中爆发区。
你以为你在做迁移,实际上是在做一次“跨方言的DDL翻译”。翻译得好不好,取决于你对两边方言细节的熟悉程度。建议所有要做这个迁移的朋友,动手之前先把源库导出的DDL整体过一遍,把和默认行为相关的隐形依赖全部显式化,再往目标库灌,能省下大把的排查时间。
另外一个小技巧:OceanBase装好后,先用DBeaver连接上去建个测试租户,把线上要迁移的DDL挑几张代表性的表在测试租户里先跑一遍,确认没问题再走批量脚本。别问我为什么强调这个,问就是我已经替你交过学费了。