【问题标题】:Rollback Transaction on Trigger ERROR触发错误时回滚事务
【发布时间】:2013-05-17 16:38:09
【问题描述】:

我正在尝试检查要插入系统的房间在该日期是否已经出租。我已经考虑过计算与房间号和日期匹配的行,然后回滚交易。但我收到以下错误,即使我已更改代码以引发用户定义的异常:

ERROR:  cannot begin/end transactions in PL/pgSQL
HINT: Use a BEGIN block with an EXCEPTION clause instead.
CONTEXT: PL/pgSQL function "checkRoom"() line 17 at SQL statement
CREATE OR REPLACE FUNCTION "checkRoom"() RETURNS TRIGGER AS
$BODY$
DECLARE 
    counter integer;
  BEGIN
    SELECT COUNT("num_sesion")
    FROM "Sesion"
    INTO counter
    WHERE "Room_Name"=NEW."Room_Name" AND "Date"=NEW."Date";

    IF (counter> 0) THEN -- Probably counter>1 as it's triggered after the transaction..
        raise notice 'THERE'S A ROOM ALREADY!!';
        raise exception 'The room is rented at that date';
    END IF;
    RETURN new;
EXCEPTION
    WHEN raise_exception THEN
        ROLLBACK TRANSACTION;
        RETURN new;
END;$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;

然后我创建触发器:

CREATE TRIGGER "roomOcupied" AFTER INSERT OR UPDATE OF "Room_Name", "Date"
ON "Sesion" FOR EACH ROW
EXECUTE PROCEDURE "checkRoom"();

距离我上一次使用 SQL 已经 2 年了,plsql 和 plpgsql 之间的变化让我抓狂。

【问题讨论】:

  • 我从来没有使用过 plpgsql,但你仍然在做 ROLLBACK TRANSACTION。你只能引发错误,事务应该被任何调用它的东西回滚。我认为:P
  • 您不需要显式回滚。除非您捕获异常并处理它,否则它会自动完成。
  • 错误信息说明了一切:您不能在 PL/pgSQL 函数内提交或回滚。任何控制执行插入或更新的事务都需要回滚事务。
  • 感谢 cmets,但问题仍然存在 :( 触发器被激活,然后异常来了,但问题是插入没有被系统回滚,即使我更改了触发器执行时刻到之前。出于这个原因,我尝试手动执行此操作。我从异常中收到消息,但有 1 行受到影响(添加的那一行):注意:已经有房间了!查询成功返回:受影响的一行,11 毫秒执行时间。

标签: sql postgresql triggers plpgsql unique-constraint


【解决方案1】:

你的触发函数有几个问题:

  • 使用IF EXISTS (...) THEN 而不是计算所有出现次数。更快,更简单。见:

  • 一个触发函数AFTER INSERT OR UPDATE可以只返回NULLRETURN NEW 仅与名为 BEFORE 的触发器相关。 The manual:

    操作后触发的行级触发器将忽略返回值,因此它们可以返回NULL

  • 不平衡的单引号。

  • 作为@Pavel explained,您无法从 plpgsql 函数中控制事务。任何未处理的异常都会强制您的整个事务自动回滚。因此,只需删除 EXCEPTION 块。

你假设的触发器被重写:

CREATE OR REPLACE FUNCTION check_room()
  RETURNS TRIGGER AS
$func$
BEGIN
   IF EXISTS (
         SELECT FROM "Sesion"    -- are you sure it's not "Session"?
         WHERE  "Room_Name" = NEW."Room_Name"
         AND    "Date" = NEW."Date") THEN
     RAISE EXCEPTION 'The room is rented at that date';
   END IF;
   RETURN NULL;
END
$func$  LANGUAGE plpgsql;

BEFORE 触发器更有意义。

但是UNIQUE INDEX ON ("Room_Name", "Date") 会做同样的事情,而且效率更高。然后,任何违反的行都会引发重复键异常并回滚事务(除非被捕获并处理)。在现代 Postgres 中,您可以选择使用 INSERT ... ON CONFLICT ... 跳过或转移此类 INSERT 尝试。见:

高级用法:

【讨论】:

    【解决方案2】:

    PostgreSQL 处理错误的方式与其他数据库截然不同。任何未处理的错误都会向用户提出。在 PL/pgSQL 中,您可以捕获任何异常或引发任何异常,但您不能显式控制事务。任何 PostgreSQL 语句都在事务内部执行(函数也是如此)。当任何未处理的异常到达顶部时,最外层的事务会自动中断。

    你能做什么:

    • 引发异常(通常在触发器中)

    BEGIN
      IF CURRENT_USER <> 'Admin' THEN
        RAISE EXCEPTION 'missing admin rights';
      END IF;
      RETURN NEW;
    END;
    
    • 捕获异常
    BEGIN
      BEGIN -- start of protected section
        -- do some, what can be stopped by exception
      EXCEPTION WHEN divide_by_zero THEN
        -- exception handler;
        RAISE WARNING 'I was here';
        -- should ignore
      EXCEPTION WHEN others THEN
        -- any unexpected exception
        RAISE WARNING 'some unexpected issue';
        RAISE; -- forward exception'
      END;

    没有其他可能性 - 所以用 PL/pgSQL 编写应用程序非常简单,但与 PL/SQL 或 TSQL 不同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-14
      • 1970-01-01
      • 2010-12-17
      相关资源
      最近更新 更多