Как повторить транзакцию после исключения в postgreSQL - PullRequest
3 голосов
/ 31 января 2012

В разных частях моего кода мне приходится повторять транзакции после исключений. Но я не могу понять, как это сделать. Вот моя тестовая функция:

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;

Ответы [ 2 ]

4 голосов
/ 31 января 2012

Для того, что вы пытаетесь сделать, LOOP внутри 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 - это зарезервированное слово в каждом стандарте SQL и, в частности, в PostgreSQL . Это плохая практика называть вашу таблицу "пользователь". Двойные кавычки делают это возможным. Это не значит, что это хорошая идея.

  • Не объявляйте переменные с тем же именем, что и столбцы таблицы, которые вы используете в теле функции. Это очень легко приводит к конфликтам имен. dummy - это переменная и имя столбца одновременно в вашем примере. Просто еще один заряженный ножной пистолет. Одна (произвольная) возможность заключается в добавлении префикса к переменным _, как я демонстрирую.

  • A FOR цикл , как я демонстрирую, намного проще, чем явная обработка курсора.

2 голосов
/ 01 февраля 2012

Благодаря вышеупомянутым комментариям я нашел решение:

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;
...