如何将 VACUUM 链接到清除例程 运行 pg_cron?

How do I chain a VACUUM off of a purge routine running with pg_cron?

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 => )
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 报告转换为另一个 table,然后 returns 结果。无论如何,我认为这个示例是让我了解存储函数中的备选方案和选项的好方法。我这里主要想实现的是删除记录,然后运行维护。如果这不适合存储函数或过程,我可以用 table 名称写一个 vacuum_list table 的条目,并有另一个工作 运行尽管那个列表。

如果有更聪明的方法来处理 vacuum 而无需额外的东西,我当然对此很感兴趣。但我也有兴趣了解可以在 PL/PgSQL 例程中组合的操作的限制。

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

COMMAND 无法执行...

对“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();

其他选项

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

当我们部署在 RDS 上时,我将查看最后两个选项。还有一个:

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

另一个想法

如果您确实想要更多的控制和一些自定义日志记录,我正在想象 table 这样的:

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);

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

最后一个选项是我感兴趣的。None 我们的 VACUUM 调用工作了相当长的一段时间,因为服务器端存在几个月前的死连接。 VACUUM 似乎 运行 很好,在这种情况下,它不能删除很多行。 (因为超旧的“开放”事务 ID、可见性地图等)看到这类事情的唯一方法似乎是 VACUUM VERBOSE 并研究输出。或者记录 vacuum 时间,更重要的是,关系大小更改以标记似乎什么都没有发生的情况,而实际上应该发生。

VACUUM 是“顶级”命令。它不能从 PL/pgSQL 或任何其他 PL 执行。