清远市网站建设_网站建设公司_原型设计_seo优化
2025/12/18 12:58:26 网站建设 项目流程

从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审计
自动化回滚预案
影子流量对比验证
最终零故障切换。如果你也在做迁移,记住:慢就是快,稳才能赢

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询