PostgreSQL 14 中仅 PL/pgSQL 编写的 PROCEDURE(用 CREATE PROCEDURE 定义、CALL 调用)支持显式 COMMIT/ROLLBACK;SQL 过程(LANGUAGE sql)因原子性设计且共享调用者事务上下文,禁止任何事务控制语句。

PostgreSQL 14 不支持在 SQL 存储过程中自主提交事务。 它只允许在 PL/pgSQL 存储过程中使用 BEGIN、COMMIT、ROLLBACK —— 而且前提是该过程用 CREATE PROCEDURE 定义,并通过 CALL 执行。SQL 过程(即用 LANGUAGE sql 定义的)本质上是“只读函数式”执行单元,无法包含事务控制语句。
为什么 LANGUAGE sql 的存储过程不能 COMMIT
SQL 过程被设计为原子性执行块:它自动包裹在外部事务中,所有语句共享调用者的事务上下文。你写在里面的 INSERT、UPDATE 等操作,实际由客户端或上层事务统一决定最终是否生效。
-
LANGUAGE sql过程内部不允许出现BEGIN、COMMIT、ROLLBACK、SAVEPOINT等关键词,否则会报错:ERROR: invalid transaction termination - 即使你尝试用动态 SQL(
EXECUTE)拼出COMMIT,也会失败 —— 因为 SQL 过程运行时无权干预事务生命周期 - 它的定位就是“轻量封装查询/简单 DML”,不是事务协调者
想自主控制事务?必须用 PL/pgSQL + CREATE PROCEDURE
只有 PL/pgSQL 编写的 PROCEDURE(不是 FUNCTION)才能显式调用 COMMIT 或 ROLLBACK,这是 PostgreSQL 11+ 引入的关键能力。
- 定义时必须用
CREATE OR REPLACE PROCEDURE,不能用CREATE FUNCTION - 过程体必须用
$$ ... $$或其他美元引用包裹,且语言指定为plpgsql - 调用时必须用
CALL,不能用SELECT—— 否则会报错:ERROR: cannot use CALL in a function - 过程内可自由使用
COMMIT,但要注意:一次COMMIT后,后续语句将在新事务中运行(即隐式开启下一个事务)
示例:
CREATE OR REPLACE PROCEDURE transfer_funds(
IN from_id INTEGER,
IN to_id INTEGER,
IN amount NUMERIC
)
AS $$
BEGIN
UPDATE accounts SET balance = balance - amount WHERE id = from_id;
UPDATE accounts SET balance = balance + amount WHERE id = to_id;
COMMIT; -- 此处真正提交
EXCEPTION
WHEN others THEN
ROLLBACK;
RAISE;
END;
$$ LANGUAGE plpgsql;调用:CALL transfer_funds(1, 2, 100.0);
常见误踩点:FUNCTION vs PROCEDURE、CALL vs SELECT
很多人卡在这一步,本质是混淆了函数与存储过程的事务权限边界。
- 用
CREATE FUNCTION写的逻辑,哪怕内容是UPDATE,也不能含COMMIT—— 即使加了LANGUAGE plpgsql也不行;它只能继承外部事务 - 用
SELECT my_func()调用过程,会触发ERROR: procedure cannot be called from a function,因为SELECT在函数上下文中运行 - 在 psql 或应用中执行
BEGIN; CALL my_proc(); COMMIT;是合法的,但CALL内部的COMMIT已经终结了该子事务,外层COMMIT实际无效(会报 warning) - 如果过程里多次
COMMIT,每次都会结束当前事务并开启新事务 —— 这容易导致部分更新被提交、部分未提交的“断裂状态”,需格外小心
最易被忽略的是:事务自主权只属于 PROCEDURE,且仅当它被 CALL 直接触发时才生效。任何试图在 SQL 过程、函数、视图或触发器中绕过这个限制的操作,都会在解析或执行阶段被拒绝。别指望语法糖能绕过引擎层的设计约束。

















