我有一个简单的 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
。
COMMIT
在上面LOOP
内进行交易?如果您想避免长时间锁定行,您还可以定义游标
WITH HOLD
,例如使用DECLARE
SQL 语句。
此类游标可以跨事务边界使用,因此您可以在一定数量的更新后
COMMIT
。您付出的代价是游标必须在数据库服务器上具体化。
由于您无法在函数中使用事务语句,因此您必须使用过程或在应用程序代码中提交。
您不能在 PostgreSQL
COMMIT
中或在 Postgres 11之前的
FUNCTION
命令中DO
(使用 PL/pgSQL 或任何其他 PL)。这导致了您报告的错误(对于 Postgres 9.5):
ERROR: cannot begin/end transactions in PL/pgSQL
Postgres 11 或更高版本中的
或
DO
语句可以 COMMIT
。参见:
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 中用另一个表的列更新一个表的列