【问题标题】:How to retry transaction after exception in postgreSQLpostgreSQL异常后如何重试事务
【发布时间】:2012-01-31 14:44:43
【问题描述】:

在我的代码的不同部分,我必须在出现异常后重试事务。但我不知道该怎么做。 这是我的测试功能:

CREATE OR REPLACE FUNCTION f() RETURNS VOID AS $$
DECLARE
    user_cur CURSOR FOR SELECT * FROM "user" WHERE id < 50 limit 10;
    row RECORD;
    counter INTEGER DEFAULT 0;
    dummy INTEGER DEFAULT 0;
BEGIN
    RAISE INFO 'Start... ';
    OPEN user_cur;
    LOOP
      FETCH user_cur INTO row;
      EXIT WHEN row IS NULL;

      BEGIN
        UPDATE "user" SET dummy = 'dummy' WHERE id = row.id;
        counter := counter + 1;
        dummy := 10 / (5 % counter);
        RAISE NOTICE 'dummy % , user_id %', (5 % counter), row.id;

      EXCEPTION WHEN division_by_zero THEN
          --What should I do here to retry transaction?
      END;
    END LOOP;
    RAISE INFO 'Finished.';

    RETURN;
END;
$$ LANGUAGE plpgsql;

【问题讨论】:

  • 为什么不检查以避免条件或句柄将异常返回给调用它的代码?
  • 遇到异常块时事务不会中止,只是数据库更改到上面的BEGIN。见postgresql.org/docs/9.1/static/…
  • 我知道 =) 我需要像 GOTO 这样的东西来再次执行 UPDATE "user" SET dummy = 'dummy' WHERE id = row.id;,在当前迭代中使用当前 row.id

标签: postgresql exception stored-procedures rollback savepoints


【解决方案1】:

对于您正在尝试做的事情,LOOP inside the LOOP 将是一个合适的解决方案。无需昂贵的异常处理。

测试设置:

CREATE TEMP TABLE usr (id int, dummy text);
INSERT INTO usr VALUES
 (1,'foo')
,(2,'bar')
,(3,'baz')
,(4,'blarg');

功能:

CREATE OR REPLACE FUNCTION x.foo()
  RETURNS VOID AS
$BODY$
DECLARE
    _r          record;
    _dummy      integer := 0;
    _ct         integer := 0;
    _5mod_ct    integer;
BEGIN
    RAISE INFO 'Start... ';
    FOR _r IN
        SELECT * FROM usr WHERE id < 50 LIMIT 10
    LOOP
        LOOP
            UPDATE usr SET dummy = 'foo' WHERE id = _r.id;

            _ct      := _ct + 1;
            _5mod_ct := 5 % _ct;

            EXIT WHEN _5mod_ct > 0; -- make sure this will be TRUE eventually!
            RAISE INFO '_5mod_ct = 0; repeating UPDATE!';
        END LOOP;

        _dummy := 10 / _5mod_ct;
        RAISE NOTICE '_5mod_ct: %, _dummy: %, user.id: %',
                      _5mod_ct, _dummy, _r.id;
    END LOOP;
    RAISE INFO 'Finished.';
END;
$BODY$ LANGUAGE plpgsql;

呼叫:

SELECT foo()

输出:

INFO:  Start...
INFO:  _5mod_ct = 0; repeating UPDATE!
NOTICE:  _5mod_ct: 1, _dummy: 10, user.id: 1
NOTICE:  _5mod_ct: 2, _dummy: 5, user.id: 2
NOTICE:  _5mod_ct: 1, _dummy: 10, user.id: 3
INFO:  _5mod_ct = 0; repeating UPDATE!
NOTICE:  _5mod_ct: 5, _dummy: 2, user.id: 4
INFO:  Finished.

您的代码示例显示了许多问题:

  • 不要使用保留字作为标识符。 user 特别是 reserved word in every SQL standard and in PostgreSQL。将您的表称为“用户”是不好的做法。双引号使之成为可能。但这并不意味着这是个好主意。

  • 不要声明与函数体中使用的表列同名的变量。这很容易导致命名冲突。 dummy 在您的示例中同时是一个变量和一个列名。只是另一个装载的脚枪。一种(任意)可能性是用_ 为变量添加前缀,就像我演示的那样。

  • 我演示的FOR loop 比显式游标处理简单得多。

【讨论】:

  • 感谢有用的建议,但我需要处理异常,我像测试一样使用“除以零”。在生产服务器上,我必须处理死锁异常等。不过还是谢谢你,我在下面的解决方案中使用了你的建议。
【解决方案2】:

感谢上面的 cmets,我找到了解决方案:

CREATE OR REPLACE FUNCTION f() RETURNS VOID AS $$
DECLARE
    row RECORD;
    counter INTEGER DEFAULT 0;
    dummy INTEGER DEFAULT 0;
BEGIN
    -- Clear user rating
    RAISE INFO 'Start... ';
    FOR row IN SELECT * FROM user_prop WHERE id < 50 limit 10 LOOP
      LOOP
        BEGIN
          UPDATE user_prop SET some_field = 'whatever' WHERE id = row.id;
          counter := counter + 1;
          dummy := 10 / (5 % counter);

          -- exit nested loop if no exception
          EXIT; 

        EXCEPTION 
          WHEN division_by_zero THEN
            -- do nothing, just repeat the loop
          WHEN deadlock_detected THEN
            -- do nothing, just repeat the loop
        END;
      END LOOP;
    END LOOP;
    RAISE INFO 'Finished.';

    RETURN;
END;
$$ LANGUAGE plpgsql;

【讨论】:

  • +1 对于此示例,不需要异常处理,但对于您提到的其他情况(死锁等),您可以充分利用异常处理与额外循环的结合。
猜你喜欢
  • 1970-01-01
  • 2011-01-06
  • 2021-05-28
  • 2017-10-07
  • 1970-01-01
  • 1970-01-01
  • 2019-01-11
  • 1970-01-01
相关资源
最近更新 更多