Цикл через CURSOR в PL / PGSQL без блокировки таблицы - PullRequest
1 голос
/ 15 октября 2019

У меня есть простой блок PL / PGSQL Postgres 9.5 , который перебирает записи в таблице и условно обновляет некоторые записи.

Вот упрощенный пример:

DO $$
  DECLARE

    -- Define a cursor to loop through records
    my_cool_cursor CURSOR FOR
    SELECT
      u.id          AS user_id,
      u.name        AS user_name,
      u.email       AS user_email
    FROM users u
    ;

  BEGIN

    FOR record IN my_cool_cursor LOOP

      -- Simplified example: 
      -- If user's first name is 'Anjali', set email to NULL
      IF record.user_name = 'Anjali' THEN
        BEGIN
          UPDATE users SET email = NULL WHERE id = record.user_id;
        END;
      END IF;

    END LOOP;
  END;

$$ LANGUAGE plpgsql;

Я бы хотел выполнить этот блок непосредственно для моей базы данных (из моего приложения, через консоль и т. Д.). Я не хочу создать FUNCTION() или хранимую процедуру для выполнения этой операции.

Проблема

Проблема заключается в том, что CURSOR и LOOP создают блокировку на уровне таблицы для моей таблицы users, поскольку все, что находится между внешним BEGIN...END, выполняется всделка. Это блокирует любые другие ожидающие запросы к нему. Если users достаточно велико, это блокирует его на несколько секунд или даже минут.

То, что я пытался

Я пытался COMMIT после каждого UPDATE, чтобы периодически очищать транзакцию и блокировку. Я был удивлен, увидев это сообщение об ошибке:

ERROR:  cannot begin/end transactions in PL/pgSQL
HINT:  Use a BEGIN block with an EXCEPTION clause instead.

Я не совсем уверен, как это сделать. Это просит меня поднять EXCEPTION, чтобы заставить COMMIT? Я попытался прочитать документацию по ошибкам перехвата , но там упоминается только ROLLBACK, поэтому я не вижу пути к COMMIT.

  1. Как мне периодически COMMIT транзакция внутри LOOP выше?
  2. В целом, мой подход даже правильный? Есть ли лучший способ перебирать записи без блокировки таблицы?

Ответы [ 2 ]

1 голос
/ 15 октября 2019

1.

Вы не можете COMMIT в PostgreSQL function или DO команда на всех (plpgsql или любой другой PL). Сообщение об ошибке, о котором вы сообщили, относится к делу (что касается Postgres 9.5):

ERROR:  cannot begin/end transactions in PL/pgSQL

A procedure может сделать это в Postgres 11или позже. См .:

Существуют ограниченные обходные пути для достижения "автономных транзакций" в старых версиях:

Но вам не нужно ничего этого для представленного случая.

2.

Используйте вместо этого простой UPDATE:

UPDATE users
SET    email = NULL
WHERE  user_name = 'Anjali'
AND    email IS DISTINCT FROM NULL;  -- optional improvement

Блокирует только те строки, которые действительно обновлены (за исключением угловых случаев). А поскольку это намного быстрее, чем CURSOR по всей таблице, блокировка также очень короткая.

Добавленное AND email IS DISTINCT FROM NULL позволяет избежать пустых обновлений. Связанный:

Редко, когда явные курсоры полезны в функциях plpgsql.

0 голосов
/ 15 октября 2019

Если вы хотите избежать блокировки строк в течение длительного времени, вы также можете определить курсор WITH HOLD, например, с помощью оператора SQL DECLARE.

Такие курсоры могутиспользоваться через границы транзакций, чтобы вы могли COMMIT после определенного количества обновлений. Цена, которую вы платите, заключается в том, что курсор должен быть материализован на сервере базы данных.

Поскольку вы не можете использовать операторы транзакций в функциях, вам придется либо использовать процедуру, либо зафиксировать в своем коде приложения.

...