从Oracle迁移到MySQL,我踩过的10个大坑(附解决方案)
- 坑1:自增主键居然不连续?
- 坑2:分页查询性能暴跌
- 坑3:大小写敏感搞崩了SQL
- 坑4:空字符串 vs NULL 的语义差异
- 坑5:日期时间精度丢失
- 坑6:没有真正的物化视图
- 坑7:PL/SQL 存储过程无法直接迁移
- 坑8:字符集和排序规则(Collation)踩雷
- 坑9:事务隔离级别行为不同
- 坑10:没有DBLink,跨库查询怎么搞?
- 最后一点真心话
差不多快10年前,当时也刚学MySQL,我们团队接到一个“光荣而艰巨”的任务:把公司用了十几年的Oracle核心系统,整体迁移到MySQL上。老板说:“开源、省钱、轻量,还能拥抱云原生。”听起来很美好,但真正动手之后才发现——迁移不是换数据库,是给系统做一场高风险手术。
今天就来和大家聊聊我在迁移过程中踩过的10个大坑,每一个都是血泪教训,也都有对应的“止血”方案。希望你少走弯路,少熬几个通宵。
坑1:自增主键居然不连续?
- 场景还原:
在Oracle里我们用的是序列(Sequence)+触发器生成主键。迁到MySQL后,改用 AUTO_INCREMENT。上线第一天,测试发现订单ID跳号了!比如刚插入1001,下一条变成1005。
- 原因分析:
MySQL的 AUTO_INCREMENT 在事务回滚、批量插入失败或服务器重启后,不会回退已分配的值。这是设计使然,不是bug。
- 解决方案:
如果业务强依赖连续ID(比如财务系统),建议继续用外部ID生成器(如Snowflake、Leaf)。
如果只是担心“看起来不连续”,那就接受现实——ID只要唯一就行,别执着于连续。
坑2:分页查询性能暴跌
- 场景还原:
Oracle里 ROWNUM <= 100 分页飞快。MySQL用 LIMIT 100000, 20 查第5000页时,直接卡死。
- 原因分析:
MySQL的 LIMIT offset, size 会先扫描前 offset 行再返回结果,offset 越大越慢。而Oracle的 ROWNUM 是在执行计划早期就过滤的。
- 解决方案:
改用 基于游标的分页(Cursor-based Pagination):
SELECT*FROMordersWHEREid>100000ORDERBYidLIMIT20;或者加缓存层,把高频分页结果预加载。
坑3:大小写敏感搞崩了SQL
- 场景还原:
开发在Oracle写的是 SELECT UserName FROM users,一切正常。迁到MySQL后报错:“Unknown column ‘UserName’”。
- 原因分析:
Oracle默认不区分列名大小写;而MySQL在Linux下表名和列名默认区分大小写(取决于 lower_case_table_names 参数)。
- 解决方案:
迁移前统一规范:所有SQL字段用小写。
设置MySQL参数 lower_case_table_names=1(仅限新实例,已有数据慎用)。
用工具(如SQL审核平台)扫描历史SQL,自动修正大小写。
坑4:空字符串 vs NULL 的语义差异
- 场景还原:
用户手机号字段在Oracle里存的是空字符串 ‘’,迁到MySQL后变成 NULL,导致前端判断逻辑全乱。
- 原因分析:
Oracle中 ‘’ 和 NULL 是等价的(这是Oracle的“特色”)。但MySQL严格区分:‘’ 是空字符串,NULL 是“无值”。
- 解决方案:
数据迁移脚本中显式处理:
INSERTINTOmysql_table(phone)SELECTCASEWHENphone=''THENNULLELSEphoneENDFROMoracle_table;应用层统一约定:要么全用 NULL,要么全用空字符串,别混用。
坑5:日期时间精度丢失
- 场景还原:
Oracle的 TIMESTAMP(6) 能存微秒,当时使用的版本低,MySQL 5.6 的 DATETIME 只支持到秒。迁移后日志时间戳全变成整秒,排查问题时根本对不上。
- 解决方案:
升级到 MySQL 5.6.4+,使用 DATETIME(6) 或 TIMESTAMP(6)。
确保应用连接串加上 serverTimezone=Asia/Shanghai,避免时区混乱。
坑6:没有真正的物化视图
- 场景还原:
Oracle里有个复杂的物化视图,每天凌晨自动刷新汇总销售数据。MySQL没有原生物化视图,怎么办?
- 解决方案:
用 普通表 + 定时任务 模拟:
CREATETABLEsales_summaryASSELECTshop_id,SUM(amount)FROMordersGROUPBYshop_id;再用 crontab 或调度系统每天凌晨重建。
或者用 ClickHouse / Doris 做实时OLAP,MySQL只存明细。
坑7:PL/SQL 存储过程无法直接迁移
- 场景还原:
核心计费逻辑全写在Oracle存储过程里,上千行PL/SQL。MySQL的存储过程语法差异大,重写成本高。
- 真实做法:
果断放弃存储过程!把逻辑上提到应用层(Java/Python)。
好处:可测、可监控、可版本控制。坏处:初期工作量大。
我们花了两周重构,但后续迭代效率提升3倍。
坑8:字符集和排序规则(Collation)踩雷
- 场景还原:
用户昵称“Àlex”在Oracle按字母排第一,在MySQL却排最后。搜索“alex”也搜不到“Àlex”。
- 原因分析:
Oracle默认用二进制或语言无关排序;MySQL默认 utf8mb4_general_ci 不支持重音符号折叠。
- 解决方案:
使用 utf8mb4_unicode_ci 或更现代的 utf8mb4_0900_ai_ci(MySQL 8.0+)。
对搜索场景,考虑引入 Elasticsearch 做全文检索,别依赖数据库LIKE。
坑9:事务隔离级别行为不同
- 场景还原:
Oracle默认是 READ COMMITTED,且不会出现幻读。MySQL InnoDB虽然也是RC,但在某些场景下仍可能看到“幻行”。
- 关键差异:
Oracle通过多版本+回滚段实现一致性读;MySQL InnoDB在RC级别下,每次SELECT都创建新快照,可能导致同一事务内两次查询结果不一致。
- 解决方案:
明确业务是否需要可重复读(RR)。如果需要,显式设置:
SETSESSIONTRANSACTIONISOLATIONLEVELREPEATABLEREAD;对账类、金融类操作,强制用RR,并配合 SELECT … FOR UPDATE。
坑10:没有DBLink,跨库查询怎么搞?
- 场景还原:
Oracle用 DBLink 轻松查另一个库的数据。MySQL没有等效功能,报表系统炸了。
- 解决方案:
不要跨库查!提前把数据同步到同一实例(用ETL工具如DataX、Canal)。
或者用 Federated 引擎(不推荐,性能差)。
更好的方式:数据仓库化,用Doris/StarRocks统一查询。
最后一点真心话
迁移不是技术炫技,而是业务连续性的保卫战。我们花了几个月做准备:
全量SQL审计
自动化回滚预案
影子流量对比验证
最终零故障切换。如果你也在做迁移,记住:慢就是快,稳才能赢。