循环通过 PL/PGSQL 中的 CURSOR 而不锁定表
Posted
技术标签:
【中文标题】循环通过 PL/PGSQL 中的 CURSOR 而不锁定表【英文标题】:Looping through a CURSOR in PL/PGSQL without locking the table 【发布时间】:2019-10-15 15:14:52 【问题描述】:我有一个简单的 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
足够大,这会将其锁定几秒钟甚至几分钟。
我尝试了什么
我尝试在每个UPDATE
之后COMMIT
,以便它定期清除事务和锁定。看到这个错误信息我很惊讶:
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
的任何方法。
-
如何在上面的
LOOP
中定期COMMIT
一个事务?
更一般地说,我的方法是否正确?有没有更好的方法来循环记录而不锁定表?
【问题讨论】:
游标不会阻塞其他(只读)查询。但我不认为需要一个(缓慢且低效的)光标开始 【参考方案1】:1.
您不能在 PostgreSQL function
或 DO
命令中使用 COMMIT
根本(plpgsql 或任何其他 PL)。您报告的错误消息很中肯(就 Postgres 9.5 而言):
ERROR: cannot begin/end transactions in PL/pgSQL
procedure
可以在 Postgres 11 或更高版本中做到这一点。见:
在旧版本中实现“自主事务”的解决方法有限:
How do I do large non-blocking updates in PostgreSQL? Does Postgres support nested or autonomous transactions? Do stored procedures run in database transaction in Postgres?但是对于本案例,您不需要任何这些。
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 函数中有用。
【讨论】:
谢谢。我给出的UPDATE
声明只是一个简化的例子。我的真实逻辑要复杂得多,所以我认为我不能在单个 UPDATE 中做到这一点作为解决方法。
@user2490003:你仍然可以。也许值得另一个关于细节的问题......【参考方案2】:
如果您想避免长时间锁定行,还可以定义游标WITH HOLD
,例如使用DECLARE
SQL 语句。
此类游标可以跨事务边界使用,因此您可以在一定数量的更新后COMMIT
。您付出的代价是游标必须在数据库服务器上实现。
由于您不能在函数中使用事务语句,您将不得不在应用程序代码中使用过程或提交。
【讨论】:
以上是关于循环通过 PL/PGSQL 中的 CURSOR 而不锁定表的主要内容,如果未能解决你的问题,请参考以下文章
PL/pgSQL“for循环”+选择基本示例(“hello world”)
使用 PostgreSQL PL/pgSQL 在 For 循环中添加月份
PL/pgSQL 函数 - 遍历特定列并在循环中执行第二个查询