相关
《Postgresql源码(60)事务系统总结》
《Postgresql源码(93)Postgresql函数内事务控制实现原理(附带Oracle对比)》
0 总结
Postgresql与Oracle都是扁平化处理函数内外的事务控制语句的:即函数内的commit也会直接把函数外面的语句提交掉,函数外面的commit也会把之前函数内部的语句提交掉。
区别是:Postgresql目前还不支持在事务块内,调用带有commit的函数;Oracle是支持的。
(即显示begin启动事务,调用带有commit的函数)
还有游标部分也有一些差别。
1 Postgresql函数内事务控制介绍
PATCH:8561e4840c81f7e345be2df170839846814fa004
增加了plpgsql中事务控制的功能,即实现在plpgsql内执行commit、rollback事务控制语句。
即下列场景
1.1 支持:procedure内的事务控制语句
CREATE TABLE test1 (a int, b text);
CREATE PROCEDURE transaction_test1()
LANGUAGE plpgsql
AS $$
BEGIN
FOR i IN 0..9 LOOP
INSERT INTO test1 (a) VALUES (i);
IF i % 2 = 0 THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
END LOOP;
END
$$;
CALL transaction_test1();
SELECT * FROM test1;
a | b
---+---
0 |
2 |
4 |
6 |
8 |
1.2 支持:匿名块内的事务控制语句
TRUNCATE test1;
DO
LANGUAGE plpgsql
$$
BEGIN
FOR i IN 0..9 LOOP
INSERT INTO test1 (a) VALUES (i);
IF i % 2 = 0 THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
END LOOP;
END
$$;
SELECT * FROM test1;
a | b
---+---
0 |
2 |
4 |
6 |
8 |
1.3 不支持:事务块内调用【带事务控制的procedure】
START TRANSACTION;
CALL transaction_test1();
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function transaction_test1() line 6 at COMMIT
COMMIT;
1.4 不支持:事务块内调用【带事务控制的匿名块】
START TRANSACTION;
DO LANGUAGE plpgsql $$ BEGIN COMMIT; END $$;
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function inline_code_block line 1 at COMMIT
COMMIT;
1.5 不支持:函数内的事务控制语句
CREATE FUNCTION transaction_test2() RETURNS int
LANGUAGE plpgsql
AS $$
BEGIN
FOR i IN 0..9 LOOP
INSERT INTO test1 (a) VALUES (i);
IF i % 2 = 0 THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
END LOOP;
RETURN 1;
END
$$;
SELECT transaction_test2();
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function transaction_test2() line 6 at COMMIT
SELECT * FROM test1;
a | b
---+---
1.6 不支持:函数内调用【带事务控制的procedure】
CREATE FUNCTION transaction_test3() RETURNS int
LANGUAGE plpgsql
AS $$
BEGIN
CALL transaction_test1();
RETURN 1;
END;
$$;
SELECT transaction_test3();
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function transaction_test1() line 6 at COMMIT
SQL statement "CALL transaction_test1()"
PL/pgSQL function transaction_test3() line 3 at SQL statement
SELECT * FROM test1;
a | b
---+---
(0 rows)
1.7 不支持:函数内调用【带事务控制的匿名块】
CREATE FUNCTION transaction_test4() RETURNS int
LANGUAGE plpgsql
AS $$
BEGIN
EXECUTE 'DO LANGUAGE plpgsql $x$ BEGIN COMMIT; END $x$';
RETURN 1;
END;
$$;
SELECT transaction_test4();
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function inline_code_block line 1 at COMMIT
SQL statement "DO LANGUAGE plpgsql $x$ BEGIN COMMIT; END $x$"
PL/pgSQL function transaction_test4() line 3 at EXECUTE
2 Oracle行为分析
2.1 procedure内的rollback会影响外面的SQL吗?:会
测试
set autocommit off;
commit;
drop table test1;
create table test1(a int);
CREATE OR REPLACE PROCEDURE transaction_test2 (x int) AS
BEGIN
INSERT INTO test1 (a) VALUES (1);
INSERT INTO test1 (a) VALUES (2);
rollback;
INSERT INTO test1 (a) VALUES (3);
INSERT INTO test1 (a) VALUES (4);
commit;
INSERT INTO test1 (a) VALUES (5);
INSERT INTO test1 (a) VALUES (6);
rollback;
END;
/
commit;
INSERT INTO test1 (a) VALUES (100);
INSERT INTO test1 (a) VALUES (200);
select * from test1;
call transaction_test2(0);
select * from test1;
commit;
select * from test1;
结果
SYS@orcl11g>select * from test1;
A
----------
3
4
2.2 procedure内的commit会影响外面的SQL吗?:会
测试
set autocommit off;
commit;
drop table test1;
create table test1(a int);
CREATE OR REPLACE PROCEDURE transaction_test2 (x int) AS
BEGIN
INSERT INTO test1 (a) VALUES (1);
INSERT INTO test1 (a) VALUES (2);
commit;
END;
/
commit;
INSERT INTO test1 (a) VALUES (100);
INSERT INTO test1 (a) VALUES (200);
select * from test1;
call transaction_test2(0);
select * from test1;
rollback;
select * from test1;
结果
SYS@orcl11g>rollback;
Rollback complete.
SYS@orcl11g>select * from test1;
A
----------
100
200
1
2.3 procedure外的rollback会影响里面的SQL吗?:会
测试
set autocommit off;
commit;
drop table test1;
create table test1(a int);
CREATE OR REPLACE PROCEDURE transaction_test2 (x int) AS
BEGIN
INSERT INTO test1 (a) VALUES (1);
INSERT INTO test1 (a) VALUES (2);
END;
/
commit;
INSERT INTO test1 (a) VALUES (100);
INSERT INTO test1 (a) VALUES (200);
select * from test1;
call transaction_test2(0);
select * from test1;
rollback;
select * from test1;
结果
SYS@orcl11g>select * from test1;
no rows selected
2.4 procedure外的commit会影响里面的SQL吗?:会
.
2.5 procedure外的savepoint
正常的savepoint是在事务内部使用的,例如:
UPDATE employees
SET salary = 7000
WHERE last_name = 'Banda';
SAVEPOINT banda_sal;
UPDATE employees
SET salary = 12000
WHERE last_name = 'Greene';
SAVEPOINT greene_sal;
SELECT SUM(salary) FROM employees;
ROLLBACK TO SAVEPOINT banda_sal;
UPDATE employees
SET salary = 11000
WHERE last_name = 'Greene';
COMMIT;
如果在事务内调用过程,过程中的事务控制语句会直接结束掉当前事务。
commit;
INSERT INTO test1 (a) VALUES (100);
INSERT INTO test1 (a) VALUES (200);
savepoint sp1;
select * from test1;
call transaction_test2(0); -- 调用这个函数的效果和执行rollback的效果是完全一样的,都会结束掉当前事务
rollback to savepoint sp1;
ERROR at line 1:
ORA-01086: savepoint 'SP1' never established in this session or is invalid
call 过程导致当前事务结束,检查点自动释放。效果和直接执行commit/rollback相同。
2 Postgresql实现函数内事务控制的源码分析
PATCH在函数调用的入口增加了atomic变量
ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic)
atomic含义?
为true表示PL内不能出现事务控制语句,否则报错。即函数、或事务块内的场景为true;过程为false。
atomic如何确定?
atomic的确定受几个函数影响,且function、procedure进入堆栈不同,总结如下:
具体调用场景中,关键变量的值:
standard_ProcessUtility函数中context | standard_ProcessUtility函数中IsTransactionBlock() | plpgsql_call_handler函数中fcinfo->context | plpgsql_call_handler函数中CallContext->atomic | |
---|---|---|---|---|
【成功】call procedure | PROCESS_UTILITY_TOPLEVEL | false | T_CallContext | false |
【失败】select function | 不走 | 不走 | null | null |
【失败】事务块内call procedure | PROCESS_UTILITY_TOPLEVEL | true | T_CallContext | true |
【成功】匿名块内call procedure | PROCESS_UTILITY_QUERY_NONATOMIC | false | T_CallContext | false |
【失败】事务块内匿名块内call procedure | PROCESS_UTILITY_QUERY_NONATOMIC | true | T_CallContext | true |
【失败】匿名块内select function | 不走 | 不走 | null | null |
*对于function来说,进入plpgsql_call_handler后fcinfo->context为空,没有后续判断。
3 怎样支持1.3?
注意事务块的状态:
- 单条SQL的事务状态是TBLOCK_STARTED,CommitTransaction可以直接提交。
- 事务块的的SQL的事务状态是TBLOCK_INPROGRESS,CommitTransaction不能直接提交。
不能仅仅使用状态机函数CommitTransaction,需要加上commit或rollback的底层处理函数。