Oracle 迁移实战:从评估到落地的全流程踩坑记录
干数据库这行,迟早会被一个灵魂拷问砸中:Oracle 到底换不换?我去年就带了一个去 Oracle 的迁移项目,前前后后折腾了大半年,跟开源数据库、国产数据库、各种同步工具都交过手,踩过的坑能装满一辆皮卡。今天不写 PPT 式总结,就从一个真实淘汰 Oracle 的案例说起,讲清楚我们为什么决定走、怎么评估、怎么迁移、后来栽在哪些地方。这篇文章适合 DBA、后端开发、技术负责人,也适合那些正在为“Oracle 还要不要续费”发愁的同行。
Oracle 是个好东西,这点没人否认,稳定性、功能完备度、生态成熟度摆在那儿。但“好”和“适合”是两码事。很多团队其实只用到了 Oracle 不到三成的能力,剩下的七成全在付授权费、养专家、解决安装和补丁问题。真到迁移那天你就发现,难点根本不在于“数据库软件本身”,而在于十几年积累的 SQL 方言、存储过程、运维习惯和人的思维惯性。
1. 为什么非淘汰 Oracle 不可:账单只是冰山一角
1.1 许可证费用的真实算法
很多人对 Oracle 收费的印象停留在“很贵”,但贵到什么程度,只有拿到报价单那一刻才有实感。Oracle 企业版采用的是按 CPU 核心数授权,不是按服务器台数,更不是按用户数。常见计费方式是每个核心乘一个核心因子,x86 服务器上通常是 0.25 到 0.5,也就是说一台 16 核的服务器,最后要买的授权数量是 4 到 8 个。以某年的公开市场报价来算,企业版一个 License 大约要几万美元,再加每年 22% 左右的维保费用。一台拥有几十个核心的数据库服务器,几年下来授权加维保的费用,足够组建一支小型开发团队。
我见过不少企业的做法是只买最小授权,然后把数据库压在一台老机器上,不敢加 CPU,不敢做集群。业务高峰期 CPU 打到 100%,问就是“加核要加钱,忍一忍”。这种被授权绑架的滋味,经历过的人都懂。相比之下,开源数据库和大多数国产数据库的授权模型就简单粗暴得多:要么免费,要么按年订阅,成本结构清晰,扩容不用先算许可证明细。
有人会说,MySQL 也有企业版,PostgreSQL 也有商业公司提供服务,但那是服务费,不是“不交钱就不能合法用”的许可费。这两者的性质完全不同。淘汰 Oracle 的第一驱动力,往往不是什么宏大叙事,就是财务总监手里的那张成本对比表。省钱本身没有错,但省钱的前提是搞清楚自己的业务到底需要什么,否则省了授权费,赔进去的是开发和运维的工时。
1.2 运维越往后越“重”:监听、补丁、ASM
Oracle 的运维复杂度是随着版本累积上去的。很多老系统还跑在 11.2.0.4 上,这个版本早过了 Premier Support,想继续用要么买延保,要么打各种补丁包。打补丁这件事听起来简单,实际上要考虑停机窗口、回滚方案、兼容性验证,一个 PSU 没打好,重启后实例起不来的情况我也见过。
日常运维里最让人头疼的,是监听器相关的故障。Oracle 的客户端连接走的是 TNS 协议,中间有 listener.ora、tnsnames.ora、sqlnet.ora 一堆配置文件,任何一个文件写错一个字符,结果就是业务方报“连不上数据库”。最常见的问题包括监听服务无法启动、ORA-28547: connection to server failed, probable Oracle Net admin error。ORA-28547 往往不是 Oracle 本身坏了,而是客户端和服务端的 Net 版本对不上,或者 sqlnet.ora 里的认证方式写错。排查的时候,第一件事不是重启,而是去看 $ORACLE_HOME/network/log/listener.log,日志里会明确告诉你失败发生在哪一层。
再往上走,还有 ASM。Oracle 的自动化存储管理(ASM)是个好东西,但也给 DBA 增加了额外的学习成本。很多人第一次进 ASM 实例时连 asmcmd 都会敲错,更不用说管理磁盘组、处理磁盘故障。你要是问一个开发了五年业务的 Java 工程师什么叫 ASM,他多半会说是汇编语言。这种复杂度直接导致一个结果:Oracle DBA 的招聘越来越难,薪酬越来越高,而且经验老道的人大多数集中在金融、电信等“老牌 Oracle 用户”里,普通企业根本抢不到。
1.3 架构与团队的双重压力
还有一个被低估的问题,是架构演进的速度。现代业务要求数据库能快速扩容、能容器化、能上云原生,而传统 Oracle 架构里的很多招式,放到 Kubernetes 环境里本身就水土不服。不是说 Oracle 不支持容器,而是把整套 RAC 架构塞进容器,复杂度会指数级上升。相比之下,PostgreSQL 和 MySQL 的云原生化路径就顺滑得多,社区里有大量成熟的 Operator 和运维模板可以抄作业。
再就是团队建设。现在的年轻开发,大学里接触最多的是 MySQL,后来慢慢有学校教 PostgreSQL,但系统学过 Oracle 的人越来越少。一个技术栈如果连招人都困难,它就很难在组织内部持续发展。我们团队里的几个 DBA 其实都是从 MySQL 转过来的,对 Oracle 的 PL/SQL、物化视图、分区表这些概念并不熟悉,每次遇到问题都要抱着文档查半天,这种隐性成本,比 Oracle 的授权费还难算。
当然,我不是劝所有人都盲目去 O。如果你们的业务重度依赖 Oracle 的 RAC、Data Guard、高级安全、分区、并行执行这些高级特性,并且用得非常好,那迁移未必划算。怕就怕在只把 Oracle 当成一个存数据的 MySQL 用,却付出了一整套企业级数据库的代价,这才是最亏的。
2. 淘汰 Oracle 前的家底盘点:先别急着写迁移脚本
2.1 业务系统到底用了多少 Oracle 特性
很多人一上来就问我,迁移要多久。这个问题没法回答,因为答案取决于你的系统到底用了多少“Oracle 特有”的东西。如果只是建表、增删改查、简单 join,那迁移工作量很低,可能用同步工具导个数据就完事。但如果存储过程有几百个、包里嵌套了自治事务、还用了 dblink 跨库查询、物化视图做报表加速,那麻烦就大了。
我建议启动迁移前,先做一个“Oracle 特性盘点”,具体做法是:
- 从
DBA_SOURCE里查一下 PL/SQL 对象的数量,按类型分组,统计有多少个存储过程、函数、包、触发器。 - 从
DBA_VIEWS、DBA_MVIEWS里看视图和物化视图的数量。 - 从
DBA_DEPENDENCIES里分析对象之间的依赖关系,哪些表被几十个存储过程引用,哪些对象是牵一发动全身的核心节点。 - 从
V$SQLAREA或 AWR 报告里抓高频执行的 SQL,看看它们在目标数据库上能否平滑运行。
这一步的核心目的,不是列出“有多少行代码”,而是搞清楚“改造成本集中在哪”。最好用表格把每个业务模块的 Oracle 特性和依赖对象列出来,按高、中、低三个等级标记改造难度。比如:
| 业务模块 | 使用的 Oracle 特性 | 改造难度 |
|---|---|---|
| 订单中心 | 存储过程、序列、rownum 分页 | 中 |
| 报表中心 | 物化视图、分析函数、dblink | 高 |
| 用户中心 | 基础增删改查、事务 | 低 |
这个盘点结果,直接决定了迁移策略:低难度模块可以整体平移,高难度模块可能需要业务侧配合重写,甚至要先做数据模型优化再迁移。
2.2 目标数据库怎么选:兼容性、成本与技术栈
去 Oracle 之后换什么?我见过三种主流方向:MySQL、PostgreSQL、以及达梦为代表的国产数据库。没有万能答案,只有适不适合。
- MySQL:生态最大,开发者熟悉度最高,运维资料多,云厂商支持好。但它在 SQL 标准兼容和复杂查询优化上相对弱一些,Oracle 里司空见惯的分析函数、CTE、物化视图在 MySQL 里支持得比较晚或者有限,存储过程语法差异也大。如果业务复杂,从 Oracle 迁过去就像让一个习惯了自动挡的人去开手动挡,需要一段适应期。
- PostgreSQL:功能厚度最接近 Oracle,支持窗口函数、CTE、递归查询、物化视图、丰富的索引类型和分区表。PL/pgSQL 和 PL/SQL 的语法有不少相似之处,数据类型映射也相对自然。我们最终选的就是 PostgreSQL,理由很简单:能用更少的改写成本承接更多 Oracle 特性。
- 达梦:语法和整体架构上与 Oracle 高度相似,很多 PL/SQL 代码几乎能原样跑起来。对于必须满足合规要求、又不能承接太高迁移成本的企业来说,达梦确实是省事的选择。但它的问题是生态相对收敛,出了问题能参考的资料比 MySQL/PG 少,DBA 也难招。如果是核心交易系统,用之前一定要做充分的压测和压力验证,别只听厂商宣传。
我们当时做选型时做了个对比矩阵:兼容性占 40%,团队熟悉度占 30%,社区生态和招聘难度占 20%,许可证成本占 10%。每一项逐条打分,最后 PostgreSQL 和达梦分差很小,考虑到团队里 Java 背景的人多、云上部署方便,最终定了 PostgreSQL。至于达梦,也没浪费,我们在另一个内部系统上试了一把,Navicat 连达梦还挺顺,整体比想象中友好。
2.3 迁移风险评估:哪些人、哪些系统、哪些成本
很多迁移项目失败,不是技术不行,而是风险没管好。我在启动之前写了一份《迁移风险评估清单》,里面有几条,后来证明每条都能救命:
- 业务连续性风险:迁移期间数据不能丢,业务中断窗口要控制在可接受范围。如果没有足够的停机窗口,就得考虑双写或同步方案。
- 兼容性风险:SQL 语法、数据类型的差异,往往要到实际跑起来才发现。建议在迁移环境里做全量 SQL 回归,把旧系统的核心查询全部录下来,逐个执行对比结果。
- 人员技能风险:团队里有没有人熟悉目标数据库?如果没有,先培训再动手。我们当时提前两个月让 DBA 和核心开发去学 PostgreSQL,还做了内部工作坊。
- 回退风险:迁移完如果发现性能不达标,能不能快速回退?所以我们在迁移期间保留了旧库只读,直到新系统稳定运行一个月后才彻底下线。
风险评估不需要做得多精细,但一定要有明确的“回退阈值”,比如“核心接口响应时间超过 500ms 超过 1 小时,就触发回退”。有了阈值,项目组成员心里就有了底,不会在灰度阶段人人大眼瞪小眼。
3. 迁移实操:表结构、SQL、数据同步的三路并进
3.1 数据类型映射:从 NUMBER 到 numeric,没那么简单
Oracle 和 PostgreSQL 的数据类型看着差不多,实际用起来全是小坑。最典型的是 NUMBER,Oracle 里一个万能类型,既能存整数、小数,又能存超高精度数字;但 PostgreSQL 里对应的是 numeric 和 integer,选错了会影响性能和用法。
我整理了一份常用的映射表,供参考:
| Oracle 类型 | PostgreSQL 类型 | 注意事项 |
|---|---|---|
| NUMBER(10) | integer / bigint | 根据长度选,避免溢出 |
| NUMBER(18) | bigint / numeric | 身份证等超长数字建议直接用 varchar |
| NUMBER(10,2) | numeric(10,2) | 金额类要保留精度 |
| VARCHAR2(n) | varchar(n) | UTF8 下注意字符和字节数差异 |
| CHAR(n) | char(n) | 两边都会补空格,尽量少用 |
| DATE | timestamp | Oracle 的 DATE 带时间,PG 的 date 不带时间,容易搞混 |
| TIMESTAMP | timestamp | 同样注意精度和时区问题 |
| CLOB | text | PG 直接上 text,简单粗暴 |
| BLOB | bytea | 二进制存储,应用层要做相应调整 |
| RAW(n) | bytea | Oracle 保留字节,PG 也是字节 |
这里最坑的是身份证号。很多老系统把身份证号存成了 NUMBER(18),导出时变成科学计数法,拷到 Excel 里后几位全变 0,这种事故我们项目组真实发生过。正确做法是:迁移前先把源库里的长数字字段统一转成字符型,比如 TO_CHAR(id_card),同步到目标库后就对应的 varchar(18),别再用数值型存证件号。
3.2 典型 SQL 改造:dual、分页、TRUNC(SYSDATE) 和存储过程
SQL 改造是迁移过程中最琐碎的活。几个高频差异点,我觉得有必要单独列出来:
Oracle 里到处是 SELECT SYSDATE FROM DUAL,PostgreSQL 里直接 SELECT now() 就行,不需要伪表。虽然 PG 也支持 SELECT 不带表,但写起来更自然。
分页 SQL 的差异更明显。Oracle 老版本用 ROWNUM:
PostgreSQL 里就一行:
写完那一刻,我身边的开发居然有点感动,因为再也不用套三层子查询了。MySQL 的 LIMIT 和 PG 基本一致,所以如果团队以前写 MySQL,学 PG 的分页几乎无感。
另一个高频函数是 TRUNC(SYSDATE),Oracle 里用来把时间截断到天。PG 里对应写法是 DATE_TRUNC('day', NOW()),或者直接 NOW()::date。很多人迁移后直接用 TRUNC(NOW()) 会报错,就是没搞清楚函数名完全不一样。
存储过程的改写是最费人力的部分。Oracle 的 PL/SQL 和 PostgreSQL 的 PL/pgSQL 语法上有七分像,但细节差异很多。比如 Oracle 的 VARCHAR2 单引号转义、异常处理、包(Package)的概念,在 PG 里都没有。一个典型的 Oracle 存储过程:
改成 PostgreSQL 的函数:
千万别小看 COMMIT 这个细节。Oracle 存储过程里自动提交很常见,但 PG 里函数运行在外部事务中,内部到处写 COMMIT 会导致函数执行报“cannot commit while a subtransaction is active”之类的错,所以函数里尽量不要管理事务,把事务控制交给应用层。
3.3 全量迁移与增量同步:工具选型和“身份证科学计数法”问题
迁移数据,工具有很多选择。市面上有 Ora2Pg、DataX、Kettle、Navicat 等,还有商业的数据库同步软件。我们当时评估下来,小表直接用 Navicat 导就行,大表用 DataX,按照“表并发 + 分批抽取”的方式跑。DataX 的好处是插件化,Oracle reader 和 PostgreSQL writer 都是现成的,配置一个 JSON 文件就能跑。
下面是一个简化的 DataX 配置模板,实际使用时要按照自己的 IP、表名和列名替换:
这里千万注意 TO_CHAR(ID_CARD) 那段,如果身份证列在 Oracle 里是 NUMBER(18),不转换直接导出,很容易变成科学计数法,导入到 PG 的 varchar 字段时,值就变成了 1.23457E+17 这种“假数据”。用 TO_CHAR 在读出阶段就转成字符串,是最稳妥的做法。
增量同步比全量麻烦得多。如果业务允许停机,最简单的方式就是停机窗口内导最后一轮增量,然后切流量。如果不能停机,就得用日志解析或基于时间戳的增量抽取。Oracle 在这方面比较封闭,官方工具要收费,开源工具对 Oracle 的支持也远不如对 MySQL 成熟。我们最后采用的是“双写 + 同步工具”的组合方案:新老库并行运行,业务侧先写新库,再异步写到老库;同时用时间戳同步历史增量。老实说,这个过程是项目里最痛苦的一段,能避则避,真遇上了,千万要先把同步延迟监控做好,别让两边数据越差越多才发现。
3.4 数据校验:没有校验的迁移都是耍流氓
数据迁移完,光看“日志无错误”是不够的,数据总量对了也不代表每条数据都对了。我当时制定了一套三层校验方案:
- 第一层:行数校验。每张表分别 count,确保源库和目标库行数一致。
- 第二层:关键字段校验。用 MD5 或 checksum 对全行的拼接值做哈希,按分片比对。比如每张表抽几列拼接后计算
MD5,源库算一遍,目标库算一遍,结果一样才放心。 - 第三层:业务样例校验。各业务线提供 10~20 条典型数据,人工查一遍界面,确认在新库上显示和旧库没差别。
这套校验看着笨,但确实发现了不少工具自动迁移时漏掉的隐性问题,比如字符集转换导致的乱码、长文本被截断、时间字段少了时区等等。做数据迁移,永远不要相信任何一个中间件“不会出错”,只信校验结果。
4. 排障实录:数据库迁移和运维中最常踩的坑
4.1 ORA-28547 与监听服务无法启动
这是 Oracle 运维里出现频率极高的故障,也是我们迁移期间被反复折腾的一个问题。先说 ORA-28547:connection to server failed, probable Oracle Net admin error,这个错通常有三种原因。
第一种是客户端和服务端的 Oracle Net 版本不匹配。我们有一个测试环境,客户端连的是 11.2 的库,但本地 sqlnet.ora 是从 12c 安装目录拷过来的,连接时报 28547。解决办法很简单:重新用 netca 配置网络组件,或者把客户端软件升级到与服务端相近的版本。
第二种是 SQLNET.AUTHENTICATION_SERVICES 配置不对。如果在 Windows 上把认证服务设置成了 NTCS,而 Oracle 服务并不是以系统用户运行,就可能认证失败,表现为连接报 ORA-28547。处理方式是改成 NONE 或 NTS,再重启监听。
第三种最玄学,是防火墙或 hosts 文件配置问题。Oracle Net 在做名字解析时要把主机名解析成 IP,如果 hosts 里写错了主机名对应关系,监听能起来但客户端就是连不上。我建议排查顺序是:先 tnsping <服务名>,能通说明网络层没问题;再 lsnrctl status 看监听服务状态;再看 listener.log 的报错时间点。
至于监听服务无法启动,多数是 ORACLE_HOME 路径变了,或者 Windows 服务的登录账号密码过期,又或者是端口被占用。我最常用的一招是打开 %ORACLE_HOME%\network\log\listener.log,看最后几条报错信息,基本就能定位,别一上来就重装。
4.2 12c 删除残留、补丁与 JRE 的连锁问题
迁移完成后,旧环境的清理也是一门学问。Oracle 的卸载做得稀烂,Windows 上尤其明显。如果你直接在“程序和功能”里点卸载,大概率会留下一堆服务和注册表项。下次再装 Oracle 12c,报 [INS-32025] 或者 PRVF-3917,十有八九是上次卸载不干净。
我总结的清理步骤是这样的:
- 停止所有 Oracle 相关服务:
services.msc里把所有带 Oracle 前缀的服务停掉。 - 用命令行删服务:
sc delete OracleServiceORCL等,逐个删干净。 - 清理注册表:
regedit里删除HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE下的相关键值,以及服务键HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services下的 Oracle 服务项。 - 删除安装目录:默认在
C:\app\<用户名>\product、C:\Program Files\Oracle,记得把隐藏目录也清掉。 - 清理环境变量:移除
ORACLE_HOME、PATH里的 Oracle 路径。
在做这些操作前,一定要先备份该备份的数据和配置文件。因为清理注册表操作很敏感,删错了其他服务也跟着遭殃。
另外有一个冷门但真实存在的坑:有些第三方工具,比如 SQL Server 的 PolyBase,要求安装 Oracle JRE 7 Update 51 才能连接 Oracle 外部数据源。如果你们在迁移期间还在维护这种桥接工具,一定要注意 JDK/JRE 版本兼容问题,别等连接报错才想起来。配置 Oracle 数据库时,自带的 JRE 版本安全性较老,浏览器和系统安全策略经常拦,需要在 Java 控制面板里把这些站点加入例外列表。
4.3 空字符串、NULL 与字符集的暗坑
Oracle 有个历史包袱:'' 空字符串等价于 NULL。PostgreSQL 严格遵守 SQL 标准,空字符串就是空字符串,NULL 就是 NULL。这意味着,如果你把 Oracle 中的数据直接倒到 PG,那些原本存空字符串的字段,在 PG 里会变成空字符串,和源库的 NULL 语义完全不同。
迁移时一定要做一次全局检查,确定业务代码里是否有类似 WHERE col = '' 的写法。如果有,要么在迁移时把空字符串统一转成 NULL,要么在应用层改掉这种依赖。另外字符集也是个隐形雷区,Oracle 常用 AL32UTF8,PG 常用 UTF8,表面上都是 UTF-8,但导入导出时如果两边数据里的特殊字符处理不一致,就会出现乱码。团队里的小姑娘曾经拿了一张包含 emoji 和生僻字的测试数据表跑全链路,就是因为这些字符最容易在转换过程中被“吃”掉。
还有日期格式,Oracle 的默认 NLS_DATE_FORMAT 是 DD-MON-RR,迁移后 PG 用的是 ISO、YMD 风格,应用层如果依赖字符串格式解析日期,务必在代码里明确格式化,不要把格式控制交给数据库默认值。
5. 迁移后的验证、优化与团队交接
5.1 性能回归与 SQL 重写
切到新库只是开始,真正的挑战是性能。Oracle 优化器有自己的一套体系,同样的 SQL,到了 PostgreSQL 里执行计划可能完全不同。以前在 Oracle 里走索引的查询,到了 PG 里可能被优化成全表扫描,不是 PG 差,而是统计信息还没更新,或者 SQL 写法吃了亏。
上线前一定要跑一遍 EXPLAIN ANALYZE,看看关键 SQL 的实际执行时间。如果发现慢查询,常见调整手段包括:
- 更新统计信息:
ANALYZE或VACUUM ANALYZE,让优化器拿到准确的表数据分布。 - 调整查询写法:比如 Oracle 里用
(+)外连接的写法,PG 里要改成标准的LEFT JOIN。 - 合理使用索引:PG 的索引类型很丰富,B-tree、Hash、GIN、BRIN,按查询场景选择。
- 调整参数:
shared_buffers、work_mem、effective_cache_size这些 PG 关键参数,默认值偏保守,需要按服务器内存重新配置。
我记得迁移后有个报表接口从原来的 200ms 涨到了 1.8 秒,排查发现是因为一个子查询在 Oracle 里自动做了物化,而 PG 里没有。后来通过改写 SQL,把子查询拆分出来用临时表承接,才把性能拉回来。这种问题很常见,没有人能一次性写完所有优化,迁移之后的第一周,开发团队几乎是抱着执行计划过的。
5.2 建立新的研发规范和知识库
迁移完成之后,最重要的是防止“又写回 Oracle 方言”。我给团队定了几条硬规矩:
- 禁止在生产环境直接查库改数据,查数一律走只读账号。
- 所有 SQL 必须经过 review,重点检查是否有
ROWNUM、NVL、SYSDATE这类 Oracle 专属写法。 - 明确事务边界,不能把 Oracle 里“存储过程内部提交”的习惯带到 PG 中。
- 新项目一律使用目标数据库的特性,不要为了迁就旧经验绕开新库的优良功能。
同时,我们把迁移过程中踩过的坑、改写的 SQL 案例、工具配置模板整理成了一个内部知识库,供后来人直接参考。很多同学可能觉得迁移完就完事了,其实团队才是最需要长期投入的地方。一次迁移如果能让团队从“只会用 Oracle”变成“真正理解目标数据库”,那这笔账怎么算都值。
我个人在实际操作中的体会是,去 Oracle 这件事,最难的从来不是数据库本身,而是对存量系统和既有习惯的管理。不要想着一步到位,先挑一两个非核心系统跑通全流程,把团队跑熟、把工具链打磨好,再逐步扩大迁移范围。最后再分享一个小技巧:迁移期间一定要保留一份“方言扫描”脚本,定期扫代码仓库里有没有残留的 Oracle 专用写法,我们就是用这个脚本,在上线后一个月里又揪出了十几处漏网的 ROWNUM 和 SYSDATE。数据库可以换,但数据敏感度和工程素养,才是这个行业里永远不能淘汰的东西。