PostgreSQL 14 中必须用 LANGUAGE plpgsql 编写支持 COMMIT/ROLLBACK 的存储过程,LANGUAGE sql 不支持事务控制;CALL 需在自动提交模式下独立执行才能使过程内 COMMIT 生效,且 INOUT 参数比 SELECT INTO 更安全可靠。

CREATE PROCEDURE 必须显式指定 LANGUAGE sql 或 plpgsql
PostgreSQL 14 支持两种主流语言编写存储过程:sql 和 plpgsql,但它们对事务控制能力完全不同。用 LANGUAGE sql 写的存储过程本质是“内联 SQL 批处理”,不支持变量、循环、异常捕获,更**不能包含 COMMIT 或 ROLLBACK** —— 即使写了也会被忽略或报错。而 LANGUAGE plpgsql 才真正支持完整的过程逻辑和事务控制。
常见错误现象:ERROR: cannot commit while a cursor is open 或直接提示 cannot execute COMMIT in a function,往往是因为误用了 CREATE FUNCTION,或者在 sql 过程里硬塞了事务语句。
- 想做事务控制?必须用
CREATE OR REPLACE PROCEDURE ... LANGUAGE plpgsql -
LANGUAGE sql过程只适合简单、无状态、单事务块的批量 INSERT/UPDATE - 即使过程体只有一条
INSERT,也建议统一用plpgsql,为后续扩展留余地
CALL 必须在事务块外执行才能触发 COMMIT
这是最常踩的坑:PROCEDURE 允许写 COMMIT,但它的生效前提是——调用者不能已经处在事务中。如果你在 BEGIN; ... CALL my_proc(); ... COMMIT; 里调用,过程里的 COMMIT 会立刻报错:cannot commit inside a transaction block。
正确做法是让 CALL 自己成为独立事务的起点:
- 直接执行
CALL my_proc();(不加任何BEGIN)→ 过程内COMMIT生效 - 若需多步骤协同提交,应把所有逻辑写进同一个
PROCEDURE,而不是拆成多个CALL - 在应用代码中(如 Python psycopg2),确保调用
CALL时连接处于自动提交模式(autocommit=True)
INOUT 参数比 SELECT INTO 更安全地返回多值
PostgreSQL 14 开始原生支持 OUT 和 INOUT 参数用于过程返回,比老式 SELECT INTO + RECORD 更清晰、类型更明确,也避免游标残留导致的事务冲突。
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
例如这个带事务分段的典型场景:插入用户并返回 ID 和状态码:
CREATE OR REPLACE PROCEDURE insert_user_with_status(
IN user_name TEXT,
IN user_email TEXT,
OUT new_id BIGINT,
OUT status_code INTEGER
) AS $$
BEGIN
INSERT INTO users (name, email, created_at)
VALUES (user_name, user_email, NOW())
RETURNING id INTO new_id;
<pre class="brush:php;toolbar:false;">status_code := 201;
COMMIT; -- 此处 COMMIT 仅在 CALL 独立执行时生效END; $$ LANGUAGE plpgsql;
- 调用:
CALL insert_user_with_status('Bob', 'bob@example.com', NULL, NULL); -
INOUT参数必须传NULL占位,否则语法报错 - 不要用
SELECT ... INTO赋值给OUT参数,直接:=更可靠
嵌套过程调用时事务上下文会继承,但 COMMIT 不会穿透
一个 PROCEDURE 里 CALL 另一个 PROCEDURE,后者若含 COMMIT,仍受“外部已有事务则禁止提交”规则约束。也就是说,事务控制权始终在最外层 CALL 所在的上下文。
这意味着:
- 不要指望子过程自己
COMMIT来“分段提交”——它只会失败 - 真需要分段(比如大批量数据分批插入+提交),必须把整个流程写在一个过程里,用
LOOP+COMMIT+EXIT WHEN控制 - 如果某步出错要回滚前面所有操作,用
EXCEPTION块捕获,再ROLLBACK TO SAVEPOINT(注意:不能用裸ROLLBACK,除非你确定没嵌套)
复杂点在于:COMMIT 不是“语句级开关”,而是“会话级契约”。哪怕过程逻辑再简单,只要涉及事务边界,就必须从调用方式开始设计。










