从 Oracle 迁移到 PostgreSQL:必备的数据库知识指南

2 阅读8分钟

如果你还在犹豫要不要把 Oracle 换成 PostgreSQL,那我先给你一个结论:这事值得干,但也别想得太简单。

这些年,“去 O” 早已不是新鲜话题。有人是因为成本,有人是想拥抱开源生态,还有人是被信创政策推着走。不管出发点是什么,一个不争的事实是:把一套跑了多年的 Oracle 数据库,丝滑地迁到 PostgreSQL 上,需要的远不止改改连接串。

这篇文章,我不打算给你堆砌一份“完美迁移清单”。我更想以一个摸爬滚打过不少迁移项目的“过来人”身份,跟你聊聊那些教科书里不会细讲、但迁移时大概率会卡住你的核心知识点。如果你是 Oracle 的老熟人,正好可以对照着查漏补缺;如果你是刚接触数据库的初学者,也能借此理解这两个“豪门”到底差异在哪。

 

对象命名:别让大小写把你坑了

先聊一个最容易踩的坑——大小写。

 

在 Oracle 的世界里,我们早就习惯了这种写法:

 

SELECT * FROM employees WHERE department_id = 10;

 

Oracle 默认会把所有对象名(表、列、索引)变成大写,你写小写也好、混合大小写也好,它都当大写处理。所以你从来不用纠结“我该用 Employees 还是 EMPLOYEES”。

但 PostgreSQL 不一样。它严格遵循 SQL 标准:未加双引号的标识符会被折叠成小写。

这意味着什么?如果你在 Oracle 里有个表叫 EMPLOYEES,迁移到 PostgreSQL 后,你要么查询时写 SELECT * FROM EMPLOYEES;(注意大写),要么迁移时就统一转成小写。更麻烦的是,如果原库里有混合大小写的对象名,比如 “Emp_Address”,那你在 PostgreSQL 里每次查询都必须带上双引号,否则就报“关系不存在”。

我的建议很直接: 迁移前,把所有对象名统一转成小写。别犹豫,也别想着“保留原样”。一套规范的小写命名,能帮你省掉后续无数的 SQL 改写和排错时间。这件事,越早做越轻松。

 

数据类型:那些“看似一样,用起来却不同”的坑

Oracle 和 PostgreSQL 的数据类型,表面上有不少一一对应的关系,但实际用起来,细节差异常常让人挠头。

 

我把几个最容易出问题的地方拎出来说说:

 

1. 字符串类型:VARCHAR2 和 VARCHAR

Oracle 里最常用的是 VARCHAR2,PostgreSQL 里是 VARCHAR。这俩在大多数场景下可以对应,但有一个关键区别:Oracle 的空字符串 '' 会被当作 NULL 处理,而 PostgreSQL 不会。

这就带来一个隐患:如果你的 Oracle 表里某个 VARCHAR2 列允许为空,但业务代码里习惯用 '' 插入“空值”,迁移到 PostgreSQL 后,这些 '' 就成了真正的空字符串,而不是 NULL。如果你的查询条件里有 WHERE column IS NULL,那些本该被查出来的数据就“消失”了。

迁移时,建议对这类字段做一次数据清洗,或者统一用 NULLIF(column, '') 做转换。别小看这个差异,线上跑起来再修数据,可就折腾了。

2. 数字类型:NUMBER 的“万能”与“代价”

Oracle 里一个 NUMBER 走天下,既能存整数也能存小数,还能指定精度。PostgreSQL 更“较真”,整数有 INT、BIGINT,小数有 NUMERIC、FLOAT,每种都有明确的存储和行为。

迁移时,如果你的 Oracle NUMBER 列没有精度限制,到了 PostgreSQL 里,用 NUMERIC 是最安全的兜底方案。但要注意:NUMERIC 在计算和索引效率上,比定长的整数类型要差一些。如果确定该列只存整数,尽量转成 BIGINT,性能和存储都会更友好。

 

3. 日期时间:时区是道坎

Oracle 里 DATE 精确到秒,TIMESTAMP 可带时区。PostgreSQL 则把时区处理得更“直白”:TIMESTAMP 不带时区,TIMESTAMPTZ 带时区。

迁移时,最稳妥的做法是:所有带时区含义的字段,统一用 TIMESTAMPTZ。PostgreSQL 内部会统一存储为 UTC,查询时按客户端时区自动转换,省心且不易出错。如果你从 Oracle 把 DATE 直接迁成 TIMESTAMP,后来发现时区对不上,补丁可就难打了。

 

SQL 语法差异:PL/SQL 与 PL/pgSQL 不是一回事

这是迁移中最“磨人”的部分。如果你的应用里存储过程、函数、触发器用得比较多,那要做好心理准备:代码基本要重写。

Oracle 的 PL/SQL 和 PostgreSQL 的 PL/pgSQL,虽然名字像,但语法细节差异很大。举几个常见例子:

l 变量声明:Oracle 用 v_count NUMBER;,PG 用 v_count INTEGER;,并且变量要在 DECLARE 块里。

l 赋值:Oracle 用 v_count := 10; 或 SELECT ... INTO,PG 也支持 :=,但 SELECT INTO 在 PG 里是用于创建新表,赋值要用 INTO 配合 SELECT ... INTO v_count。

l 异常处理:Oracle 的 EXCEPTION WHEN NO_DATA_FOUND THEN 在 PG 里变成了 EXCEPTION WHEN NO_DATA_FOUND THEN,但内部变量和上下文机制不同,不能直接搬。

l 游标:Oracle 的显式游标写法在 PG 里需要调整,隐式游标的 FOR rec IN (SELECT ...) LOOP 写法倒是比较接近,但细节上也有差异。

我的建议是: 不要试图做“自动翻译”。市面上有一些迁移工具可以做语法转换,但转换出来的代码往往需要大量人工修正。更务实的做法是:评估这些存储过程的业务逻辑,能上移到应用层的就上移,实在需要保留在数据库里的,用 PL/pgSQL 重新实现。

 

事务与并发:从“乐观”到“MVCC”

Oracle 和 PostgreSQL 都支持 MVCC(多版本并发控制),但实现机制不同,这直接影响了应用的并发行为。

Oracle 通过 undo 表空间实现读一致性,写操作不阻塞读操作。PostgreSQL 则通过在数据页内保留多个元组版本来实现 MVCC。这意味着:

 

l 写操作会留下“死元组”,需要定期 VACUUM 来清理,否则表会膨胀,性能下降。

l 长事务对 PostgreSQL 更不友好。一个运行很久的事务,会阻止 VACUUM 清理它启动后产生的死元组,导致表膨胀加剧。

 

如果你从 Oracle 迁移过来,必须建立 VACUUM 的运维习惯。这不是可选项,而是必选项。同时,要检查应用里是否有长事务、长连接的使用模式,适当优化或拆解。

 

另外,锁机制也有差异。Oracle 里习惯用 SELECT ... FOR UPDATE 做行级锁,PG 同样支持,但要注意 PG 的 FOR UPDATE 在并发场景下的行为和等待机制略有不同,建议在迁移后对核心并发场景做一轮压测,别想当然。

 

工具链与生态:换个赛道,换个玩法

Oracle 有成熟的 EM(Enterprise Manager)、SQL Developer、RMAN 等一套完整的运维体系。PostgreSQL 的生态更“开放”,但也更“散装”。

l 备份恢复:Oracle 的 RMAN 是“一体化”的,PG 这边多用 pg_dump、pg_basebackup 配合 WAL 归档。逻辑备份和物理备份的职责划分更清晰。

l 高可用:Oracle 有 RAC,PG 这边主流方案是 Patroni + etcd + 流复制,或者云厂商托管服务。如果你原来习惯了 RAC 的“一个集群多个节点”模式,切换到 PG 的“主备 + 读写分离”模式,可能需要调整应用对数据库连接的预期。

l 监控诊断:Oracle 的 AWR 报告非常强大,PG 没有完全对等的“一站式”工具,但通过 pg_stat_statements、pg_stat_activity、pg_stat_user_tables 等系统视图组合,也能做到深度诊断。关键在于,团队要重新学习一套“看指标”的方式。

 

这方面,我不想说谁好谁坏。但如果你决定迁移,就要做好准备:数据库团队的管理习惯和工具链,会迎来一次重构。早点建立新的监控面板、备份恢复演练、故障切换预案,比到了生产环境再摸索要稳妥得多。

 

最后

从 Oracle 到 PostgreSQL,不是简单的“换个数据库”,而是一次对团队技术栈、运维习惯、甚至应用架构的全面梳理。

这条路,我见过走得顺畅的,也见过半路折返的。那些走得顺的项目,往往有一个共同特点:不低估差异,不迷信工具,给测试和验证留足了时间。

迁移本身确实复杂——对象要适配、SQL 要改写、数据要校验、性能要压测。但翻过这座山之后,你得到的不仅是一套更灵活、成本更可控的数据库底座,还有一个对数据库底层理解更深、更“懂行”的团队。

 

如果你正在做这件事,或者刚刚开始评估,希望这篇文章能帮你少踩几个坑。这条路值得走,但别急着赶路,把脚下的每一步踩实了,比什么都重要。关于数据库迁移这件事,重庆思庄数据库研究院在过去十几年里,陪伴不少客户走完了从 Oracle 到 PostgreSQL 的完整路径。如果你在迁移过程中遇到具体的技术卡点,或者想找一份更贴合你场景的迁移评估清单,欢迎来找我们聊聊。