如何从使用 pg_cron 运行的清除例程中链接 VACUUM?

Posted

技术标签:

【中文标题】如何从使用 pg_cron 运行的清除例程中链接 VACUUM?【英文标题】:How do I chain a VACUUM off of a purge routine running with pg_cron? 【发布时间】:2021-11-30 13:52:34 【问题描述】:

Postgres 13.4

我设置了一些pg_cron 作业,以定期从类似日志的文件中删除旧记录。我想做的是在执行清除后运行VACUUM ANALYZE。不幸的是,我无法弄清楚如何在存储函数中执行此操作。我错过了一个技巧吗?存储过程更合适吗?

作为一个例子,这是我的清除例程之一

CREATE OR REPLACE FUNCTION dba.purge_event_log (
    retain_days_in integer_positive default 14)

RETURNS int4

AS $BODY$

WITH  -- Use a CTE so that we've got a way of returning the count easily.
deleted AS (

-- Normal-looking code for this requires a literal:
-- where your_dts < now() - INTERVAL '14 days'
-- Don't want to use a literal, SQL injection, etc.
-- Instead, using a interval constructor to achieve the same result:

   DELETE
     FROM dba.event_log
    WHERE dts < now() - make_interval (days => $1)
RETURNING *
),

----------------------------------------
-- Save details to a custom log table
----------------------------------------
logit AS (
insert into dba.event_log (name, details)
    values ('purge_event_log(' || retain_days_in::text || ')',
             'count = ' || (select count(*)::text from deleted)
           )
)

----------------------------------------
-- Return result count
----------------------------------------
select count(*) from deleted;

$BODY$
  LANGUAGE sql;

COMMENT ON FUNCTION dba.purge_event_log (integer_positive) IS
'Delete dba.event_log records older than the day count passed in, with a default retention period of 14 days.';

事实上,我并不真正关心这个例程的count(*) 结果,在这种情况下。但我可能想要一个结果在其他一些类似的上下文中的附加操作。如您所见,例程删除记录,使用 CTE 将insert 报告到另一个表中,然后返回结果。无论如何,我认为这个示例是让我了解存储函数中的替代方案和选项的好方法。我在这里要实现的主要是删除记录,然后运行维护。如果这不适合存储函数或过程,我可以使用表名写出vacuum_list 表的条目,然后通过该列表运行另一个作业。

如果有一个更聪明的方法来接近vacuum 而不需要额外的,我当然对此感兴趣。但我也有兴趣了解可以在 PL/PgSQL 例程中组合的操作的限制。

Pavel Stehule 的回答是正确且完整的。我决定在这里跟进一下,因为我喜欢深入研究代码中的错误、Postgres 中的行为等,以便更好地了解我正在处理的内容。我在下面包含了一些注释,供任何发现它们有用的人使用。

无法执行命令...

对“VACUUM 不能在事务块内执行”的引用为我提供了一种更好的方法来搜索文档以查找类似的受限命令。以下信息可能无法涵盖所有​​内容,但这是一个开始。

Command                Limitation
CREATE DATABASE
ALTER DATABASE         If creating a new table space.
DROP DATABASE
CLUSTER                Without any parameters.
CREATE TABLESPACE
DROP TABLESPACE
REINDEX                All in system catalogs, database, or schema.

CREATE SUBSCRIPTION    When creating a replication slot (the default behavior.)
ALTER SUBSCRIPTION     With refresh option as true.
DROP SUBSCRIPTION      If the subscription is associated with a replication slot.

COMMIT PREPARED
ROLLBACK PREPARED
DISCARD ALL

VACUUM

接受的答案表明该限制与使用的特定服务器端语言无关。我刚刚遇到一个较旧的线程,其中有一些关于存储函数和事务的出色解释和链接:

Do stored procedures run in database transaction in Postgres?

示例代码

我还想知道存储过程,因为它们可以控制事务。我在 PG 13 中尝试了它们,不,代码被视为存储函数,直至错误消息。

对于从事此类工作的任何人,这里是 SQL 和 PL/PgSQL 存储函数和过程的“hello world”示例,用于测试 VACCUM 在这些情况下的行为方式。剧透:它不工作,如宣传的那样。

SQL 函数

/*
select * from dba.vacuum_sql_function();

Fails:
ERROR:  VACUUM cannot be executed from a function
CONTEXT:  SQL function "vacuum_sql_function" statement 1. 0.000 seconds. (Line 13).
*/

DROP FUNCTION IF EXISTS dba.vacuum_sql_function();

CREATE FUNCTION dba.vacuum_sql_function()
RETURNS VOID
LANGUAGE sql

AS $sql_code$

VACUUM ANALYZE activity;

$sql_code$;

select * from dba.vacuum_sql_function(); -- Fails.

PL/PgSQL 函数

/*
select * from dba.vacuum_plpgsql_function();

Fails:
ERROR:  VACUUM cannot be executed from a function
CONTEXT:  SQL statement "VACUUM ANALYZE activity"
PL/pgSQL function vacuum_plpgsql_function() line 4 at SQL statement. 0.000 seconds. (Line 22).
*/

DROP FUNCTION IF EXISTS dba.vacuum_plpgsql_function();

CREATE FUNCTION dba.vacuum_plpgsql_function()
RETURNS VOID
LANGUAGE plpgsql

AS $plpgsql_code$

BEGIN
VACUUM ANALYZE activity;
END

$plpgsql_code$;

select * from dba.vacuum_plpgsql_function();

SQL 过程

/*
call dba.vacuum_sql_procedure();

ERROR:  VACUUM cannot be executed from a function
CONTEXT:  SQL function "vacuum_sql_procedure" statement 1. 0.000 seconds. (Line 20).
*/

DROP PROCEDURE IF EXISTS dba.vacuum_sql_procedure();

CREATE PROCEDURE dba.vacuum_sql_procedure()
LANGUAGE SQL

AS $sql_code$

VACUUM ANALYZE activity;

$sql_code$;

call dba.vacuum_sql_procedure();

PL/PgSQL 过程

 /*
call dba.vacuum_plpgsql_procedure();

ERROR:  VACUUM cannot be executed from a function
CONTEXT:  SQL statement "VACUUM ANALYZE activity"
PL/pgSQL function vacuum_plpgsql_procedure() line 4 at SQL statement. 0.000 seconds. (Line 23).
*/

DROP PROCEDURE IF EXISTS dba.vacuum_plpgsql_procedure();

CREATE PROCEDURE dba.vacuum_plpgsql_procedure()
LANGUAGE plpgsql

AS $plpgsql_code$

BEGIN
VACUUM ANALYZE activity;
END

$plpgsql_code$;

call dba.vacuum_plpgsql_procedure();

其他选项

很多。据我了解,在 Postgres 中运行的服务器端代码不支持 VACUUM 和其他一些命令。因此,您的代码需要从其他地方开始。可以是:

无论cron 在您的服务器操作系统中使用什么。 任何您喜欢的外部客户。 pg_cron

当我们部署在 RDS 上时,最后两个选项就是我要看的地方。还有一个:

AUTOVACCUM 和偶尔的VACCUM 做他们的事。

这很容易做到,而且似乎可以很好地满足我们的大部分需求。

另一个想法

如果您确实想要更多控制和一些自定义日志记录,我想像这样的表格:

CREATE TABLE IF NOT EXISTS dba.vacuum_list (
    database_name   text,
    schema_name     text,
    table_name      text,
    run             boolean,
    run_analyze     boolean,
    run_full        boolean,
    last_run_dts    timestamp)

ALTER TABLE dba.vacuum_list ADD CONSTRAINT
   vacuum_list_pk
   PRIMARY KEY (database_name, schema_name, table_name);

这只是一个草图。思路是这样的:

当桌子需要清理时,您将 INSERT 转换为 vacuum_list,至少就您而言。

在我的情况下,这将是一个UPSERT,因为我不需要一个完整的类似日志的表,每个感兴趣的表只需要一行,最后一个结果和/或挂起状态。

远程客户端等周期性地连接,读取表,并根据记录中指定的选项执行每个指定的VACUUM

外部客户端使用上次运行时间戳更新该行,以及您在该行中包含的任何其他内容。

或者,您可以包括持续时间字段和关系大小的更改前:后真空。

最后一个选项是我感兴趣的。我们的VACUUM 呼叫在相当长的一段时间内都没有工作,因为服务器端存在几个月前的死连接。 VACUUM 似乎运行良好,在这种情况下,它无法删除很多行。 (因为超级旧的“开放”事务 ID、可见性映射等)看到这类事情的唯一方法似乎是 VACUUM VERBOSE 并研究输出。或者记录真空时间,更重要的是,记录关系大小的变化,以标记似乎什么都没有发生的情况。

【问题讨论】:

在 pg_cron 中添加真空,就在这个函数之后。 【参考方案1】:

VACUUM 是“***”命令。它永远无法从 PL/pgSQL 或任何其他 PL 执行。

【讨论】:

感谢您的澄清。有谁知道文档是否包含我可以检查的讨论?我想更好地了解我可以从存储函数中做什么和不能做什么。 @MorrisdeOryx - postgresql.org/docs/current/sql-vacuum.html VACUUM 不能在事务块内执行。,任何 PL/pgSQL 代码都应该在事务下执行。我认为最重要的命令是 CREATE DROP DATABASE 和 VACUUM。

以上是关于如何从使用 pg_cron 运行的清除例程中链接 VACUUM?的主要内容,如果未能解决你的问题,请参考以下文章

TableView:如何从 didSelectRowAtIndexPath 例程中选择另一行的值

从 VBA 中的另一个子例程中抑制 MsgBox

如何让 VBA 子例程调用将数组传递给子例程中的另一个函数的函数

ActiveX 多线程调用 javascript 回调例程中的问题

在不同的私有子例程中使用公共声明的数组时下标超出范围

rpc Call 方法是不是与服务器在同一个 go 例程中运行?