Oracle 迁到 IvorySQL,到底能少改多少代码?我实测了一遍

本文作者:施嘉伟,12 年数据库从业经验,Oracle ACE Pro、PostgreSQL ACE,OCM/PGCM/KCM 认证。 IvorySQL 专家顾问委员、KVA、崖山 YVP、KWDB MVP、PolarDB 开源社区/HaloDB 技术顾问、TiDB 社区技术布道师、青学会 MOP 技术社区专家顾问。

去 O 的项目一旦进入实施阶段,团队问的问题就变得很具体:原来的 SQL 还能不能跑?PL/SQL 要改多少?客户端要不要换?事务语义会不会变?

文档里写高度兼容 Oracle,落到这四个问题上,通常说不清楚。

所以我干脆在一台 Linux 8 的机器上,把 Oracle 19c、PostgreSQL 18 和 IvorySQL 5.4 装在一起,用同一套业务场景各跑一遍。

说明:TPS、QPS 一律不测。三个库的参数、内存分配、存储布局都不一样,跑出来的数字没有可比性,贴出来只会误导人。这次只看 Oracle 语法、Package、转账事务、客户端连接和日常运维。

一、测试环境

三个库跑在同一台 linux 服务器上。

项目配置
操作系统Oracle Linux Server 8.10
CPU4 核
内存23 GiB
Oracle19c Enterprise Edition 19.3.0.0.0
Oracle 实例orcl,非 CDB,READ WRITE
PostgreSQL18.4,PGDG 官方 RPM
IvorySQL5.4,底层 PostgreSQL 18.4
字符集Oracle 为 AL32UTF8,PostgreSQL 和 IvorySQL 为 UTF8
数据库端口说明
Oracle 19c1521Oracle Listener
IvorySQL 5.45432PostgreSQL 协议连接
IvorySQL 5.41522ivorysql.port 独立监听端口
PostgreSQL 1855432仅绑定 127.0.0.1 的测试实例

三个服务都由 systemd 托管。IvorySQL 跑在 ivorysql 用户下,数据目录 /var/lib/ivorysql/5.4/data,页校验和已启用。

二、先说结论

IvorySQL 5.4 的 Oracle 兼容能力,确实明显高于原生 PostgreSQL。NUMBER、VARCHAR2、空字符串转 NULL、DUAL、NVL、DECODE、SYSDATE、序列的 .NEXTVAL、PL/iSQL 和 Package,这次全部通过。

测试项Oracle 19cIvorySQL 5.4PostgreSQL 18
空字符串视为 NULL通过通过不同语义
NVL、DECODE、DUAL、SYSDATE通过通过需要改写
NUMBER、VARCHAR2通过通过需要改写
sequence.NEXTVAL通过通过需要改写
Oracle Package通过通过不支持 Package
CONNECT BY通过失败失败
ROWNUM通过失败失败

数据类型、函数和 Package 这一层,IvorySQL 能省掉相当一部分首轮改造。但 CONNECT BY、ROWNUM、客户端协议和企业级能力,还得单独评估。

三、安装体验:像 PostgreSQL,但要留意双端口

IvorySQL 5.4 官方提供 x86_64 RPM。装完之后软件目录是 /usr/ivory-5,核心命令还是那几个老朋友:initdb、pg_ctl、psql、pg_dump、pg_basebackup。

初始化 Oracle 模式实例:

/usr/ivory-5/bin/initdb \
  -D /var/lib/ivorysql/5.4/data \
  -U ivorysql \
  -m oracle \
  --data-checksums \
  --encoding=UTF8 \
  --locale=en_US.utf8 \
  --auth-local=peer \
  --auth-host=scram-sha-256

初始化完成后,用 pg_ctl 启动数据库:

/usr/ivory-5/bin/pg_ctl \
  -D /var/lib/ivorysql/5.4/data \
  -l logfile \
  start

输出:

waiting for server to start.... stopped waiting
pg_ctl: could not start server
Examine the log output.

第一次启动就失败了。Oracle Listener 占着 1521,而 IvorySQL 的 ivorysql.port 默认也是 1521,日志报完端口冲突,进程直接退出。

把独立端口改成 1522:

ivorysql.listen_addresses = '*'
ivorysql.port = 1522

PostgreSQL 协议那条继续留在 5432。改完之后两个端口都正常监听。

和 Oracle 同机部署的话,装之前先看一眼 1521 有没有人占。

lsof -i :1521

四、基础 Oracle 语义对比

1. 空字符串与 NULL

Oracle 把空字符串当 NULL,PostgreSQL 认为这是两个不同的值。这个差异不只是写法问题,它会直接影响非空约束、条件判断、唯一索引,以及应用层的参数校验。

测试 SQL:

SELECT CASE
         WHEN CAST('' AS VARCHAR) IS NULL THEN 'NULL'
         ELSE 'NOT NULL'
       END AS empty_string_semantics;

结果:

数据库结果
Oracle 19cNULL
IvorySQL 5.4NULL
PostgreSQL 18NOT NULL

本次 Oracle 模式实例中,IvorySQL 的 ivorysql.enable_emptystring_to_NULL 是 on。迁移老应用时,这一条能省掉不少应用层的判空适配。

但也不能马虎大意。空字符串转 NULL 本身就会改变一部分约束的判定结果,历史数据、索引和接口参数还是得挨个看过去。

2. NVL、DECODE、DUAL 和 SYSDATE

几条 Oracle 里天天写的 SQL,在 IvorySQL 里直接跑通了:

SELECT NVL(CAST(NULL AS VARCHAR), CAST('fallback' AS VARCHAR));
SELECT DECODE(2, 1, 'one', 2, 'two', 'other');
SELECT SYSDATE FROM dual;

IvorySQL 分别返回 fallback、two 和当前日期。同样的语句丢给原生 PostgreSQL,报的是 NVL 函数不存在、dual 表不存在,得改成 COALESCECASECURRENT_TIMESTAMP

这里有个坑容易被忽略。IvorySQL 的会话要先进 Oracle 兼容模式,才会按 Oracle 语法解析:

SET ivorysql.compatible_mode = oracle;

如果客户端把 SET 和后面的 Oracle SQL 塞进同一个协议消息发过去,服务端可能会用原来的模式先把整批语句解析掉。所以迁移工具应该在连接建立之后单独设一次会话模式,再发业务 SQL。

3. NUMBER、VARCHAR2 与序列

后面的转账案例直接用了这张表:

CREATE TABLE account_balance (
  account_id NUMBER PRIMARY KEY,
  account_name VARCHAR2(50) NOT NULL,
  balance NUMBER(18,2) NOT NULL,
  updated_at DATE DEFAULT SYSDATE NOT NULL
);

Oracle 19c 和 IvorySQL 5.4 都建成功了。Oracle 风格的序列访问 IvorySQL 也认:

CREATE SEQUENCE transfer_seq START WITH 1 INCREMENT BY 1;
SELECT transfer_seq.NEXTVAL FROM dual;

原生 PostgreSQL 这边,类型要换成 NUMERIC、VARCHAR、TIMESTAMP,序列调用改写成:

SELECT nextval('transfer_seq');

即便类型能对上,精度、默认值、隐式转换和日期计算这几处仍然要逐个核对。

五、没通过的两个:CONNECT BY 和 ROWNUM

层次查询用的是 Oracle 里最常见的写法:

SELECT LEVEL
FROM dual
CONNECT BY LEVEL <= 3;

Oracle 19c 返回 1、2、3。IvorySQL 5.4 报错:

ERROR: syntax error at or near "BY"

ROWNUM 同样没过:

ERROR: "rownum": invalid identifier

在 IvorySQL 和 PostgreSQL 里,这两类写法可以按场景换成递归 CTE、generate_series、窗口函数或者 FETCH FIRST:

SELECT ROW_NUMBER() OVER (ORDER BY value) AS rn, value
FROM (VALUES (10), (20), (30)) t(value)
FETCH FIRST 2 ROWS ONLY;

这两个才是迁移评估里最容易漏的东西。组织树、菜单树、地区层级、ROWNUM 分页,这类 SQL 一般不在表结构里,而是散在报表、存储过程和 ORM 的自定义查询里。只对着 Schema 做兼容性扫描,根本发现不了。开工前必须把这两类 SQL 数清楚。

六、核心测试:账户转账 Package

建了两个账户:

账号姓名初始余额
1001Alice1000.00
1002Bob500.00

转账过程做四件事:锁定付款账户、检查余额、更新双方余额、写转账日志。先转 125.50,再提交一笔 99999 的转账验证异常路径。

Oracle 的 Package 接口:

CREATE OR REPLACE PACKAGE pkg_transfer AS
  PROCEDURE transfer(
    p_from_account NUMBER,
    p_to_account NUMBER,
    p_amount NUMBER
  );
  FUNCTION balance_of(p_account_id NUMBER) RETURN NUMBER;
END pkg_transfer;
/

Package Body 里用 SELECT … FOR UPDATE 锁住付款账户,余额不足时 Oracle 调 raise_application_error(-20001, ‘insufficient balance’)。

IvorySQL 用的 Package 和 Package Body 结构几乎没动,主要改了异常写法,换成 PL/iSQL 支持的形式:

IF v_from_balance < p_amount THEN
  RAISE EXCEPTION 'insufficient balance';
END IF;

调用方式还是包名加过程名:

CALL pkg_transfer.transfer(1001, 1002, 125.50);

PostgreSQL 18 没有 Package,只能把过程和函数放进 Schema 里组织,用 PL/pgSQL 重写一遍,表结构也得换成 NUMERIC、VARCHAR、CURRENT_TIMESTAMP。

三个库的成功转账结果一致:

账号转账后余额
1001874.50
1002625.50

转账日志各生成一条 SUCCESS 记录,金额 125.50。余额不足的那笔都抛了异常,没有二次扣款,两个账户仍然是 874.50 和 625.50。

这部分最能说明 IvorySQL 的价值在哪。业务结果上,原生 PostgreSQL 一样能做到,代价是开发要重写类型、函数、Package 组织方式和部分过程语法。IvorySQL 把 Oracle Package 的结构原样留了下来,对写惯 PL/SQL 的人来说,代码读起来是熟的,迁移时心里有底。

七、psql -f 执行 Package 脚本验证

对 Oracle 风格的 Package 脚本做了补充验证。执行命令:

PGHOST=127.0.0.1 PGPORT=1521 PGUSER=ivorysql ${IVY_BIN_DIR}/psql -f a.sql

输出:

CREATE PACKAGE
CREATE PACKAGE BODY

测试中,psql -f 可以直接执行包含 /终止符的 Package 和 Package Body 脚本,没有出现脚本被提前截断的问题。

八、SQLPlus 能直连 IvorySQL 吗

IvorySQL 配置里有个 ivorysql.port,这次设成了 1522。我分别用 psql 和 Oracle 19c 的 SQLPlus 去连这个端口。

psql 连上了:

PostgreSQL 18.4 (IvorySQL 5.4)
PSQL_STATUS=0

SQLPlus 这样连:

sqlplus ivorysql/******@//127.0.0.1:1522/ivory_compare

客户端返回:

ORA-12537: TNS:connection closed
SQLPLUS_STATUS=249

服务端同时记了一条:

invalid length of startup packet

就这次官方 5.4 RPM 的实际表现来看,1522 端口收的仍然是 PostgreSQL 启动包,不能当 Oracle TNS Listener 用。依赖 SQL*Plus、OCI、Oracle JDBC Thin 或者固定 TNS 连接串的应用,驱动和连接层要单独验证,改个 IP 和端口是过不去的。

这个结果直接决定了应用要不要换驱动这个判断。SQL 语法兼容和网络协议兼容,是两件事,得分开验。

九、运维体验与软件占用

三个软件目录的占用:

软件目录占用
Oracle 19c Home7.0 GiB
IvorySQL 5.4487 MiB
PostgreSQL 1848 MiB

这几个数字只反映安装包内容,跟性能没关系。IvorySQL 的 RPM 打包了比较多的扩展、客户端和空间数据组件,所以比基础的 PGDG 包大一截。

运维命令和 PostgreSQL 基本一致:

systemctl status ivorysql
/usr/ivory-5/bin/pg_isready -h 127.0.0.1 -p 5432
/usr/ivory-5/bin/psql -d ivory_compare
/usr/ivory-5/bin/pg_dump -d ivory_compare

PostgreSQL DBA 那套 WAL、VACUUM、备份恢复、日志排查的经验可以直接接过来用。Oracle DBA 则要补 MVCC、Autovacuum、角色权限和执行计划工具这几块。

备份策略、归档、监控、主备、故障切换、升级演练,生产上还得一项项补齐。本文只验了单实例功能,高可用和灾备不下结论。

十、三个库怎么选

Oracle 19c

已经深度用上 PL/SQL、RAC、Data Guard、分区、审计和商业工具链的核心系统,继续用 Oracle 是合理的。企业功能成熟,厂商兜底,代价是授权、技能和运维成本。

PostgreSQL 18

新系统、云原生应用,或者团队本来就愿意按 PostgreSQL 原生方式开发,直接上 PG。生态足够成熟。从 Oracle 迁过来的话,SQL、过程语言和应用驱动要做一次系统性改造,这笔账要提前算。

IvorySQL 5.4

想进 PostgreSQL 生态,又不想在第一轮就承担全部 Oracle 改造量的项目,IvorySQL 值得做 PoC。以下几种情况优先考虑:

  • 业务里大量使用 NUMBER、VARCHAR2、NVL、DECODE、序列和 Package;
  • 团队熟悉 PL/SQL,希望保留过程代码的组织方式;
  • 计划逐步替换 Oracle,能接受一部分 SQL 和客户端改造;
  • 需要开源数据库方案,并且愿意投入建设 PostgreSQL 运维能力。

反过来,下面这几种情况不能简单做决定:

  • 大量使用 CONNECT BY、ROWNUM 和其他 Oracle 专有 SQL;
  • 应用强依赖 SQL*Plus、OCI、TNS 或 Oracle JDBC 的协议行为;
  • 核心链路依赖 RAC、Data Guard、专有诊断包和复杂分区;
  • 项目要求不改 SQL、不改驱动、不改发布脚本。

十一、我的判断

在这次转账测试中,IvorySQL 5.4 跑通了 Oracle 数据类型、序列、Package、Package Body、SELECT FOR UPDATE 行锁和异常处理,最终业务结果与 Oracle 19c、PostgreSQL 18 一致。

兼容能力可以减少迁移改造量,但迁移前的检查不能省。CONNECT BY、ROWNUM、SQL*Plus 连接方式和脚本终止符都需要逐项扫描。团队还要处理对象转换、应用驱动适配和业务回归测试。

评估 IvorySQL 时,我更愿意把它看成“带 Oracle 兼容层的 PostgreSQL”。数据库架构、部署和运维沿用 PostgreSQL 体系,数据类型、函数和 PL/SQL 语法向 Oracle 靠拢。存量 Oracle 系统可以少改一部分 SQL 和存储过程。新系统是否采用,取决于团队是否需要 Oracle 兼容,以及团队对 PostgreSQL 技术栈的掌握程度。

选型前,团队应拿真实表结构、核心 SQL、存储过程和并发事务做 PoC。本文的转账案例只验证了一个典型事务链路,不能代表整个系统的迁移结果。

**IvorySQL 项目地址:

https://github.com/IvorySQL/IvorySQL**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值