Oracle 与 PostgreSQL 事务系统对比

John Doe 十一月 25, 2025

事务系统是关系数据库的核心组件,它提供的服务有助于保障应用程序的数据完整性。SQL 标准对数据库事务的部分特性进行了规范,但未明确许多细节。因此,不同关系数据库的事务系统可能存在显著差异。如今,许多企业正尝试从 Oracle 数据库迁移至 PostgreSQL。要将应用程序从 Oracle 迁移到 PostgreSQL,理解两者事务系统的差异至关重要,否则可能会遭遇影响性能和数据完整性的意外情况。

image

ACID:数据库事务提供的服务

此处的 “ACID” 这一缩写代表以下四项核心特性:

  • 原子性(Atomicity):确保单个数据库事务中的所有语句构成一个不可分割的单元,要么所有语句执行成功,要么所有语句的效果都不生效。这一特性需应对包括硬件故障在内的各类问题。
  • 一致性(Consistency):确保任何数据库事务都不会违反数据库中定义的约束。
  • 隔离性(Isolation):确保并发执行的事务不会导致“异常情况”(即数据库出现任何串行执行事务无法产生的状态)。
  • 持久性(Durability):确保已提交完成的数据库事务永远无法撤销,即使发生系统崩溃或局部硬件故障也不例外。

下文将对这些特性类别进行详细分析。

Oracle 与 PostgreSQL 事务的相似之处

首先,有必要说明 Oracle 和 PostgreSQL 在事务管理方面的相同之处。幸运的是,许多重要特性都是相同的:

  1. 两种数据库系统均采用多版本并发控制:读操作与写操作互不阻塞。当有事务正在执行更新或删除操作时,读操作会获取数据的旧版本。
  2. 两种数据库系统均会将锁持有至事务结束。
  3. 两种数据库系统均将行锁存储在行本身,而非锁表中。因此,锁定行可能会导致额外的磁盘写入,但无需进行锁升级。
  4. 两种数据库系统均支持SELECT ... FOR UPDATE语句,用于显式并发控制。关于两者在该语句上的差异,下面进一步探讨。
  5. 两种数据库系统均以 READ COMMITTED 作为默认事务隔离级别,且该级别在两者中的行为非常相似。

Oracle 与 PostgreSQL 原子性对比

在原子性的实现上,两种数据库存在一些细微差异:

自动提交

Oracle 中任何数据操纵语言(DML)语句都会隐式启动一个数据库事务(除非已有事务处于开启状态)。用户必须通过COMMITROLLBACK语句显式结束这些事务,Oracle 没有专门用于启动事务的语句。

PostgreSQL 默认运行在自动提交模式下。除非使用START TRANSACTIONBEGIN显式启动多语句事务,否则每条语句都会在独立的事务中执行。在这种单语句事务结束时,PostgreSQL 会自动执行COMMIT

许多数据库 API 支持关闭自动提交功能。由于 PostgreSQL 服务器本身不支持直接禁用自动提交,客户端会通过在适当时候自动发送BEGIN语句来模拟“非自动提交模式”。使用这类 API 时,用户无需担心上述差异。

语句级回滚

Oracle 中若某条 SQL 语句执行出错,并不会终止整个事务。Oracle 只会回滚该出错语句的执行效果,事务仍可继续进行。若要回滚整个事务,需手动处理错误并调用ROLLBACK语句。

PostgreSQL 与之相反,若事务中的某条 SQL 语句执行出错,整个事务会被中止。在事务中止后,PostgreSQL 会忽略后续所有语句,直至用户通过ROLLBACKCOMMIT结束事务(此时两种语句的效果均为回滚事务)。

大多数编写规范的应用程序不会受此差异影响,因为通常情况下,用户会希望回滚所有导致错误的事务。但在某些场景下,PostgreSQL 的这一行为可能会带来不便:例如,在长时间运行的批处理任务中,若输入数据存在问题导致语句出错,用户可能希望仅处理该错误,而无需回滚此前已完成的所有操作。这种情况下,应在 PostgreSQL 中使用(符合 SQL 标准的)保存点(SAVEPOINT),但这可能需要对应用程序进行相应修改。需注意的是,在 PostgreSQL 中应谨慎使用保存点:保存点基于子事务实现,过多使用会严重影响性能。

事务性数据定义语言(DDL)

Oracle 中任何数据定义语言(DDL)语句都会自动执行COMMIT,因此无法回滚 DDL 语句的操作效果。

PostgreSQL 不存在这一限制。除少数例外情况(如VACUUMCREATE DATABASECREATE INDEX CONCURRENTLY等),所有 SQL 语句的操作效果均可回滚。

Oracle 与 PostgreSQL 一致性对比

两种数据库在一致性方面的差异较少,均能确保事务不会违反约束。

值得一提的是,Oracle 允许通过ALTER TABLE语句启用或禁用约束。例如,用户可先禁用某一约束,执行违反该约束的数据修改操作,然后通过ENABLE NOVALIDATE重新启用约束(对于主键和唯一约束,此操作仅在约束设置为DEFERRABLE时有效)。

而在 PostgreSQL 中,仅有超级用户能禁用实现外键约束、可延迟唯一约束及可延迟主键约束的触发器;同时,也只有超级用户能设置 session_replication_role = replica(这是禁用此类触发器的另一种方式)。

Oracle 与 PostgreSQL 中主键和唯一约束的验证时机

以下SQL脚本在 Oracle 数据库中执行时不会报错:

CREATE TABLE tab (id NUMBER PRIMARY KEY);

INSERT INTO tab (id) VALUES (1);
INSERT INTO tab (id) VALUES (2);

COMMIT;

UPDATE tab SET id = id + 1;

COMMIT;

但等效的脚本在 PostgreSQL 中执行时会报错:

CREATE TABLE tab (id numeric PRIMARY KEY);

INSERT INTO tab (id) VALUES (1);
INSERT INTO tab (id) VALUES (2);

UPDATE tab SET id = id + 1;
ERROR:  duplicate key value violates unique constraint "tab_pkey"
DETAIL:  Key (id)=(2) already exists.

导致这一差异的原因是:PostgreSQL 会逐行验证约束(违反 SQL 标准),而 Oracle 会在语句执行结束后验证约束。若要使 PostgreSQL 的行为与 Oracle 一致,需将约束创建为DEFERRABLE(延迟检查)类型,此时 PostgreSQL 会在语句执行结束后验证约束。

Oracle 与 PostgreSQL 隔离性对比

隔离性是 Oracle 与 PostgreSQL 差异最大的领域之一,Oracle 对事务隔离级别的支持相对有限。

PostgreSQL 与 Oracle 事务隔离级别的对比

SQL 标准定义了四种事务隔离级别:READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)和SERIALIZABLE(串行化)。但与标准通常的详细程度相比,对各隔离级别的定义较为模糊。例如,标准指出在READ UNCOMMITTED隔离级别下可能发生“脏读”(读取其他事务未提交的数据),但未明确这是要求还是可选的行为。

Oracle 仅支持READ COMMITTEDSERIALIZABLE两种隔离级别。但后者实际上是“伪串行化”,Oracle 提供的是快照隔离。例如,以下并发事务在 Oracle 中均可成功执行(缩进部分代表第二个会话):

CREATE TABLE tab (name VARCHAR2(50), is_highlander NUMBER(1) NOT NULL);

-- start a new serializable transaction
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

SELECT count(*) FROM tab WHERE is_highlander = 1;
  COUNT(*)
----------
	 0

                              -- start a new serializable transaction
                              SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

                              SELECT count(*) FROM tab WHERE is_highlander = 1;
                                COUNT(*)
                              ----------
	                               0

-- the count is zero, so let's proceed
INSERT INTO tab VALUES ('MacLeod', 1);

COMMIT;

                              -- the count is zero, so let's proceed
                              INSERT INTO tab VALUES ('Kurgan', 1);

                              COMMIT;

而事务的串行执行无法产生上述结果,若按串行方式执行,第二个事务查询时的计数应为 1。

此外,Oracle 的SERIALIZABLE实现还存在缺陷。例如,若创建表时未指定SEGMENT CREATION IMMEDIATE(立即创建段),之后在SERIALIZABLE事务中尝试插入第一行数据时,会触发序列化错误。从技术角度而言,这一行为符合规范(在较高隔离级别下需应对序列化错误),但本质上是 Oracle 为避免实现复杂操作(如索引页拆分)而随意抛出的序列化错误。因此,在 Oracle 数据库中,SERIALIZABLE隔离级别几乎无法实际使用。

PostgreSQL 支持全部四种隔离级别,但会将READ UNCOMMITTED自动升级为READ COMMITTED(这一行为是否符合 SQL 标准存在争议)。其中,SERIALIZABLE是真正的串行化隔离级别。PostgreSQL 的REPEATABLE READ隔离级别行为与 Oracle 的SERIALIZABLE类似,但前者的稳定性和可用性远优于后者。

READ COMMITTED 隔离级别下并发数据修改的对比

READ COMMITTED作为默认隔离级别,属于较低的隔离级别,仍可能发生多种异常情况。这里简要说明以下场景:

  1. 事务 1 修改了某表的一行数据,但未提交;
  2. 事务 2 执行某条语句(可能是 SELECT ... FOR UPDATE)尝试锁定该行数据,随后进入等待状态;
  3. 事务 1 提交。

此时,事务 2 会得到什么结果?在 Oracle 和 PostgreSQL 中,使用READ COMMITTED隔离级别时,事务 2 均能读取到最新提交的数据,但两者存在细微差异:

  • PostgreSQL:仅重新评估被锁定的行,速度较快,但可能导致结果不一致;
  • Oracle:会重新执行完整查询,速度较慢,但能保证结果一致。

Oracle 与 PostgreSQL 持久性对比

两种数据库系统均通过事务日志实现持久性:Oracle 中称为 REDO 日志,PostgreSQL 中称为 WAL 预写式日志。在持久性保障方面,Oracle 与 PostgreSQL 提供的保证完全一致。

Oracle 与 PostgreSQL 事务的其他差异

除上述特性外,Oracle 与 PostgreSQL 的事务系统还存在以下差异:

事务大小和持续时间的限制

这一差异源于两种数据库实现多版本并发控制的根本方式不同:

  • Oracle:通过 UNDO 表空间存储被修改行的旧版本。因此,Oracle 事务中数据修改操作的数量受限于 UNDO 表空间的大小。对于大批量删除或更新操作,Oracle 中的常见做法是“分批执行+中间提交”。
  • PostgreSQL:直接在表中存储行的多个版本,因此不存在类似 Oracle 的事务大小限制。但大批量更新操作会导致表膨胀,因此也建议分批执行更新(并在批次之间运行VACUUM语句)。不过,在 PostgreSQL 中,无需限制大批量删除操作的规模。

此外,长时间运行的事务在所有关系数据库中都是问题,它们会持有锁,增加阻塞其他会话的风险,且更容易引发死锁。而在 PostgreSQL 中,长时间运行的事务问题更为突出:它还可能导致自动清理(autovacuum)维护任务停滞,进而引发难以修复的表膨胀。

SELECT … FOR UPDATE 语句的对比

两种数据库均支持SELECT ... FOR UPDATE语句,用于同时读取并锁定行数据,且均支持NOWAITSKIP LOCKED子句。差异在于:PostgreSQL 不支持WAIT <integer>子句(指定等待锁的时长),需通过动态调整lock_timeout参数实现类似功能。

更重要的差异是:在 PostgreSQL 中,若仅需更新行数据,不应使用FOR UPDATE。除非计划删除行或修改主键/唯一键列,否则应使用FOR NO KEY UPDATE锁模式。

事务 ID 回绕

事务ID回绕是 PostgreSQL 特有的问题。PostgreSQL 的多版本并发控制通过为每行存储事务 ID 来管理行版本的可见性。这些事务 ID 由 32 位计数器生成,最终会出现回绕(数值达到最大值后重新从最小值开始计数)。PostgreSQL 需执行特殊的维护操作,以避免在回绕过程中丢失数据。在高事务量的系统中,这一问题可能成为需要专门关注和调优的痛点。

结论

在大多数方面,Oracle 与 PostgreSQL 的事务机制运行方式相似,但两者仍存在差异。若计划将应用从 Oracle 迁移至 PostgreSQL,需仔细审视这些差异。本文的对比分析有助于识别迁移过程中可能出现的问题。