Oracle 和 PostgreSQL 中不同的默认错误处理

Different default error handling in Oracle and PostgreSQL

我在 PL/SQL (PL/pgSQL) 代码中遇到错误后比较了 Oracle 和 PostgreSQL 的默认行为。为此,我编写了如下所示的类似 Oracle 和 PostgreSQL 代码。

甲骨文代码(db<>fiddle):

CREATE TABLE table1 (col1 int);

CREATE PROCEDURE raise_error AS
BEGIN
  INSERT INTO table1 VALUES (1/0);
END;
/

INSERT INTO table1 VALUES (1);
CALL raise_error();
COMMIT;
SELECT * FROM table1;

PostgreSQL 代码(db<>fiddle):

CREATE TABLE table1 (col1 int);

CREATE PROCEDURE raise_error() AS $$
BEGIN
  INSERT INTO table1 VALUES (1/0);
END;
$$ LANGUAGE plpgsql;

BEGIN TRANSACTION; -- disable auto-commit

INSERT INTO table1 VALUES (1);
CALL raise_error();
COMMIT;
SELECT * FROM table1;

注意:在 PostgreSQL 中,我另外 运行 BEGIN TRANSACTION 语句来禁用自动提交,因为 Oracle 没有自动提交,我希望这两个代码是类似的。

SELECT * FROM table1 查询的结果在 Oracle 中是一行,在 PostgreSQL 中没有行。

如您所见,Oracle 和 PostgreSQL 中的类似代码给出了不同的结果。默认错误处理中出现这种差异的原因是什么?

在 Oracle 中,您使用两个单独的事务,第一个成功,第二个失败。在 PostgreSQL 中,您明确告诉它只使用一个事务并一起处理语句。

在 Oracle 中,如果您使用 PL/SQL 匿名块将语句分组到单个事务中:

BEGIN
  INSERT INTO table1 VALUES (1);
  raise_error();
END;
/

并且,在 PostgreSQL 中等效:

DO
$$
BEGIN
  INSERT INTO table1 VALUES (1);
  CALL raise_error();
END;
$$ LANGUAGE plpgsql;

然后 table 中将没有行,因为过程中的异常将回滚整个事务。


或者,在 Oracle 中,您可以这样做:

INSERT INTO table1 VALUES (1);

DECLARE
  divide_by_zero EXCEPTION;
  PRAGMA EXCEPTION_INIT( divide_by_zero, -1476 );
BEGIN
  raise_error();
EXCEPTION
  WHEN DIVIDE_BY_ZERO THEN
    ROLLBACK;
END;
/

将两个事务回滚到最后一次提交的效果相同。

db<>fiddle Oracle PostgreSQL

Oracle 和 PostgreSQL 这里的行为确实不同。

Oracle 具有我称之为“语句级回滚”的功能:如果事务中的语句 运行 导致错误,则仅回滚该语句的影响,事务继续。

在PostgreSQL中,事务内部的任何错误都会中止整个事务,所以你只能回滚事务,它根本没有任何影响。这更符合“全有或全无”的精神,但据我所知,SQL 标准对此并不具体,因此可以对两种行为进行争论。

但是,您可以在 PostgreSQL 中使用符合标准的保存点从事务中的错误中“恢复”:

START TRANSACTION;

INSERT INTO table1 VALUES (1);

/* set a savepoint we can revert to */
SAVEPOINT x;

CALL raise_error();

ROLLBACK TO SAVEPOINT x;

/* now the INSERT can be committed */
COMMIT;

但请注意,您每笔交易 don't use too many savepoints(不超过 64),否则性能可能会受到影响。