在PL / PGSQL中循环访问CURSOR而不锁定表

时间:2019-10-15 15:14:52

标签: sql postgresql transactions plpgsql postgresql-9.5

我有一个简单的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()或存储过程来执行此操作。

问题

问题是CURSORLOOP在我的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?我尝试阅读the documentation on Trapping Errors,但只提到了ROLLBACK,所以看不到COMMIT的任何方式。

  1. 如何在上述COMMIT内定期LOOP进行交易?
  2. 更一般地说,我的方法是否正确?有没有更好的方法来循环记录而不锁定表?

2 个答案:

答案 0 :(得分:1)

1。

您不能在PostgreSQL functionDO命令 (plpgsql或任何其他PL)中glUniform()。您报告的错误消息到此为止(就Postgres 9.5而言):

COMMIT

procedure可以在Postgres 11或更高版本中实现。参见:

在较早的版本中,实现“自主交易”的解决方法有限:

但是在本案例中,您不需要任何一个。

2。

改为使用简单的ERROR: cannot begin/end transactions in PL/pgSQL

UPDATE

仅锁定实际更新的行(带有例外情况)。而且由于整个表的速度比UPDATE users SET email = NULL WHERE user_name = 'Anjali' AND email IS DISTINCT FROM NULL; -- optional improvement 快得多,因此锁定也非常简短。

添加的CURSOR避免空更新。相关:

在ppgsql函数中很少使用显式游标。

答案 1 :(得分:0)

如果要避免长时间锁定行,则还可以定义游标WITH HOLD,例如使用DECLARE SQL语句。

可以在事务边界上使用此类游标,因此可以在进行一定数量的更新后COMMIT。您要付出的代价是必须在数据库服务器上实现游标。

由于不能在函数中使用事务处理语句,因此您将不得不使用过程或提交应用程序代码。