使用 psycopg2 调用 postgres 存储过程 - 无效的事务终止

Posted

技术标签:

【中文标题】使用 psycopg2 调用 postgres 存储过程 - 无效的事务终止【英文标题】:Calling a postgres stored procedure with psycopg2 - invalid transaction termination 【发布时间】:2021-11-09 22:23:57 【问题描述】:

我正在尝试使用 psycopg2 调用一个过程(来自 pg_partman run_maintenance_proc())。

我可以从 psql 命令行执行一个简单的CALL partman.run_maintenance_proc();。 但是当我尝试对 psycopg2 做同样的事情时,我遇到了这个错误:

psycopg2.errors.InvalidTransactionTermination: invalid transaction termination
CONTEXT:  PL/pgSQL function partman.run_maintenance_proc(integer,boolean,boolean) line 43 at COMMIT

这是我的代码:

dbconn = psycopg2.connect(t_dsn)
cursor = dbconn.cursor()
cursor.execute('CALL partman.run_maintenance_proc()')
results = cursor.fetchone()
cursor.close()
dbconn.close()

您知道可能出了什么问题吗?

编辑:程序代码

CREATE PROCEDURE @extschema@.run_maintenance_proc(p_wait int DEFAULT 0, p_analyze boolean DEFAULT NULL, p_jobmon boolean DEFAULT true)
    LANGUAGE plpgsql
    AS $$
DECLARE
 
v_adv_lock              boolean;
v_row                   record;
v_sql                   text;
v_tables_list_sql       text;
 
BEGIN
 
v_adv_lock := pg_try_advisory_lock(hashtext('pg_partman run_maintenance'));
IF v_adv_lock = false THEN
    RAISE NOTICE 'Partman maintenance already running or another session has not released its advisory lock.';
    RETURN;
END IF;
 
v_tables_list_sql := 'SELECT parent_table
            FROM @extschema@.part_config
            WHERE undo_in_progress = false
            AND automatic_maintenance = ''on''';
 
FOR v_row IN EXECUTE v_tables_list_sql
LOOP
/*
 * Run maintenance with a commit between each partition set
 * TODO - Once PG11 is more mainstream, see about more full conversion of run_maintenance function as well as turning 
 *        create_partition* functions into procedures to commit after every child table is made. May need to wait
 *        for more PROCEDURE features as well (return values, search_path, etc).
 *      - Also see about swapping names so this is the main object to call for maintenance instead of a function.
 */
    v_sql := format('SELECT %I.run_maintenance(%L, p_jobmon := %L',
        '@extschema@', v_row.parent_table, p_jobmon);
 
    IF p_analyze IS NOT NULL THEN
        v_sql := v_sql || format(', p_analyze := %L', p_analyze);
    END IF;
        
    v_sql := v_sql || ')';
 
    RAISE DEBUG 'v_sql run_maintenance_proc: %', v_sql;
 
    EXECUTE v_sql;
    COMMIT;
 
    PERFORM pg_sleep(p_wait);
 
END LOOP;
 
PERFORM pg_advisory_unlock(hashtext('pg_partman run_maintenance'));
END
$$;

注意:我注意到有一个cursor.callproc 方法,但它似乎执行了一个SELECT。如果我使用 callproc 会出现错误:HINT: To call a procedure, use CALL.

数据库:postgres 12.5 / Psycog2 版本:2.9.1

【问题讨论】:

文档Callproc 对此进行了解释。事实上在psycopg(psycopg3) 的新版本中没有callproc。要获得错误的答案,您需要使用run_maintenance_proc() 中的代码更新问题。最好的猜测是您在程序中没有正确处理事务。 确实这就是我对 callproc 的看法。我用程序代码更新了问题 我不明白@extschema@ 在做什么。这不是一个合法的标识符,我没有看到它在哪里被替换?这个过程是在别的什么地方运行的吗? 【参考方案1】:

啊,我想我看到了这个问题。从这里Transaction control:

可以将连接设置为自动提交模式:这样所有执行的命令都将立即提交,并且不可能回滚。一些命令(例如 CREATE DATABASE、VACUUM、使用事务控制的存储过程调用……)需要在任何事务之外运行:为了能够从 Psycopg 运行这些命令,连接必须处于自动提交模式:您可以使用自动提交属性。

【讨论】:

【参考方案2】:

如果您使用 psycopg2 进行连接,则可以使用

cursor.autocommit=1 cursor.execute('CALL partman.run_maintenance_proc()')

如果您通过 sqlalchemy 连接,则无法使用此功能, 对于 sqlalchemy:在创建引擎时将 autocommit 设置为 True 例如)

导入 sqlalchemy

engine = sqlalchemy.create_engine('postgresql://test',isolation_level="AUTOCOMMIT")

【讨论】:

令人惊讶的是,这东西居然对我有用

以上是关于使用 psycopg2 调用 postgres 存储过程 - 无效的事务终止的主要内容,如果未能解决你的问题,请参考以下文章

Postgres:使用psycopg2或附近未终止的引用字符串

Python/postgres/psycopg2:获取刚刚插入的行的 ID

用户 postgres 的 Psycopg2 对等身份验证

更新 - 用于postgres的psycopg2游标

从 psycopg2 在 Postgres 中插入 dict 列表作为 json 数组

如何使用Flask,SQLAlchemy或psycopg2从Postgres中的光标获取数据