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

问题描述 投票:0回答:2

我有一个简单的 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. 更一般地说,我的方法正确吗?有没有更好的方法在不锁定表的情况下循环记录?
sql postgresql transactions plpgsql postgresql-9.5
2个回答
1
投票

如果您想避免长时间锁定行,您还可以定义游标

WITH HOLD
,例如使用
DECLARE
SQL 语句。

此类游标可以跨事务边界使用,因此您可以在一定数量的更新后

COMMIT
。您付出的代价是游标必须在数据库服务器上具体化。

由于您无法在函数中使用事务语句,因此您必须使用过程或在应用程序代码中提交。


1
投票

1.

您不能在 PostgreSQL

COMMIT
 中或在 Postgres 11 
之前的
FUNCTION
 命令中
DO(使用 PL/pgSQL 或任何其他 PL)。这导致了您报告的错误(对于 Postgres 9.5):

ERROR: cannot begin/end transactions in PL/pgSQL

Postgres 11 或更高版本中的 

PROCEDURE

DO 语句可以
COMMIT
。参见:

    PostgreSQL 无法在 PL/pgSQL 中开始/结束事务
  • 在 PostgreSQL 中,“存储过程”和其他类型的函数有什么区别?
  • 在旧版本中实现“自主事务”的解决方法有限:

    如何在 PostgreSQL 中进行大型非阻塞更新?
  • Postgres 支持嵌套或自治事务吗?
  • 过程是否在 PostgreSQL 的事务中运行?
  • 但是对于所呈现的案例,您不需要任何这些。

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
避免了空更新。相关:

在 PostgreSQL 中用另一个表的列更新一个表的列

© www.soinside.com 2019 - 2024. All rights reserved.