如何从 PostgreSQL 事务内部记录自定义消息?

pie*_*rop 7 postgresql log

我有一个生成交易并执行它们的软件。我想根据执行的查询占查询总数的简单比例异步检查事务进度。我能够读取stdoutstderr生成的psql,所以我想一个解决方案可以打印到stdout一些自定义消息,如“进度:3/8”(或另一个自定义文本字符串)。

我曾考虑过执行“日志查询”,该查询将有关查询进度的信息存储在适当的表中,但该表在事务完成之前不可用,这使我无法检查事务进度。

目前我尝试了以下操作(假设在交易中有 3 个查询来做一些事情):

BEGIN;
CREATE OR REPLACE FUNCTION progress(curr int, total int) RETURNS float AS $$
BEGIN
  RAISE NOTICE '___PROGRESS___%', curr/total::float;
  RETURN curr/total::float;
END;

-- First query
SELECT ... FROM ...;
-- Log the progress
SELECT * FROM progress(1,3);

-- Second query
SELECT ... FROM ...;
-- Log progress
SELECT * FROM progress(2,3);

-- Third query
SELECT ... FROM ...;
-- Log progress
SELECT * FROM progress(3,3);

COMMIT;
Run Code Online (Sandbox Code Playgroud)

我使用以下语法从 bash 文件执行脚本:

res=$((psql -U username -d db -h localhost --log-file=plog.log -f "the transaction above") 2>&1>clog.log)
Run Code Online (Sandbox Code Playgroud)

我使用前面的 bash 命令从类似上述事务的事务中得到三个输出:plog.log,clog.log$resbash 变量。我将发布其中的一些摘录,以明确它们的内容。

日志文件

....
********* QUERY **********
SELECT * from progress(1, 3);
**************************

     progress      
-------------------
 0.333333333333333
(1 row)

...
********* QUERY **********
SELECT * from progress(2, 3);
**************************

     progress      
-------------------
 0.6666666666666667
(1 row)

...
********* QUERY **********
SELECT * from progress(3, 3);
**************************

     progress      
-------------------
 1
(1 row)
Run Code Online (Sandbox Code Playgroud)

日志文件

...
     progress      
-------------------
 0.333333333333333
(1 row)

...
     progress      
-------------------
 0.6666666666666667
(1 row)

...
     progress      
-------------------
 1
(1 row)    
Run Code Online (Sandbox Code Playgroud)

$res变量(psql返回值)

psql:/path/to/script.sql:11: NOTICE:  ___PROGRESS___0.333333333333333
psql:/path/to/script.sql:16: NOTICE:  ___PROGRESS___0.666666666666667
psql:/path/to/script.sql:21: NOTICE:  ___PROGRESS___1
Run Code Online (Sandbox Code Playgroud)

问题是我无法实时访问$res变量的内容。我可以改为访问clog.logand的内容plog.log,因此 的使用RAISE NOTICE根本没有意义,因为它没有出现在clog.logand 中,plog.log而只出现在$res.

我应该坚持解析clog.log还是plog.log为了SELECTprogress()函数中找到输出值(删除该函数中的RAISE NOTICE语句,因为它无用)还是有替代方法?我错过了什么吗?

谢谢

Dan*_*ité 6

RAISE NOTICE是正确的方法。

RAISE NOTICE输出未出现的原因clog.log是您的标准错误重定向在 shell 级别是错误的。

你要:

psql --log-file=plog.log -f file.sql  >clog.log 2>&1
Run Code Online (Sandbox Code Playgroud)

这样,它将标准输出重定向到标准输出clog.log,然后将标准错误重定向到该标准输出。

问题中的完成方式:2>&1>clog.log由于顺序错误而无法实现,如 bash 手册中所述:

请注意,重定向的顺序很重要。例如,命令

ls > 目录列表 2>&1

将标准输出(文件描述符 1)和标准错误(文件描述符 2)定向到文件目录列表,而命令

ls 2>&1 > 目录列表

仅将标准输出定向到文件 dirlist,因为在标准输出重定向到 dirlist 之前,标准错误已生成标准输出的副本。


Ane*_*n N 0

在我们的系统中,我们使用 dblink 并写入同一个数据库。这是 Oracle 自治事务的另一种方法。

这里我使用DB链接的概念来实现日志记录的异步提交。这与Oracle中的自治事务的概念相同。在数据库链接内完成的提交不会导致主事务中的提交。

实际数据通过命令“PERFORM PUBLIC.dblink_exec”写入日志表并立即提交。

当主程序仍在执行时,您可以通过从不同会话查询日志表来跟踪主程序的进度,无论执行数小时后是否出错,日志数据仍保持提交状态。

#1: Confirm dblink extension

#2: Create DDLs
CREATE TABLE dblink_log (ID serial, message TEXT);--Table to store the logs

GRANT ALL ON TABLE core.dblink_log TO PUBLIC;--Permission to write as a dblink

GRANT ALL ON TABLE core.dblink_log_id_seq TO PUBLIC;

#3
/*Test block to write to a table and commit while 
  the main transaction is still in progress*/
DO$$ 
DECLARE
        open_connections TEXT [] := PUBLIC .dblink_get_connections () ; 
        --Cannot connect more than once before closing
        my_dblink_name TEXT := 'logging_dblink' ;
        --Define a name for your db link connection
    BEGIN

    --If the connection doest not exists, create one
    IF open_connections IS NULL 
       OR NOT open_connections @> ARRAY [ my_dblink_name ] THEN
        PERFORM PUBLIC .dblink_connect (
            my_dblink_name,
            'host=127.0.0.1  port=5432 dbname=dbname user=user password=password'
        ) ; 
        raise notice 'New db link connection made' ;
    ELSE
        raise notice 'Db link connection exists, re-using' ;
    END IF ; 

    FOR i IN 1..10 loop 

        PERFORM PUBLIC.dblink_exec (
                my_dblink_name,
                'BEGIN;
                 INSERT INTO core.dblink_log (message) 
                 VALUES (''Executing loop #' || i || ''') ;
                 COMMIT;'
            ) ; 

        perform pg_sleep(10) ;-- Execute select * from core.dblink_log
                              -- in a different session
    END loop ;
END $$;

#4: From a different session verify record core.dblink_log is populated with data
Run Code Online (Sandbox Code Playgroud)