I have some procedure which when i execute in dbeaver works fine without issue, however when i call it from outside program i am getting error below. I do not want to copy/paste full procedure here because it’s pretty big and it works in db tool. I just copy/paste top and bottom. What could be the cause of it?
Procedure:
CREATE OR REPLACE PROCEDURE MyProcedure(lot of args..)
LANGUAGE plpgsql
AS $procedure$
DECLARE
.....
.....
COMMIT;
END;
$procedure$
;
Error:
ERROR: invalid transaction termination
Where: PL/pgSQL function MyFunction line 185 at COMMIT Call getNextException to see other errors in the batch. Line 185 at COMMIT Call getNextException to see other errors in the batch.
asked Dec 3, 2019 at 14:15
The documentation says:
Transaction control is only possible in
CALL
orDO
invocations from the top level or nestedCALL
orDO
invocations without any other intervening command. For example, if the call stack isCALL proc1()
→CALL proc2()
→CALL proc3()
, then the second and third procedures can perform transaction control actions. But if the call stack isCALL proc1()
→SELECT func2()
→CALL proc3()
, then the last procedure cannot do transaction control, because of theSELECT
in between.
There are some other, undocumented, restrictions:
-
You cannot start a transaction explicitly with
BEGIN
and commit it inside a transaction. So the following will fail:START TRANSACTION; CALL procedure_with_commit();
This may be improved in future releases.
-
All procedures in the call stack must be written in PL/pgSQL:
CREATE PROCEDURE b() LANGUAGE plpgsql AS 'BEGIN PERFORM 42; COMMIT; END;'; CREATE PROCEDURE a() LANGUAGE sql AS 'CALL b()'; CALL a(); ERROR: invalid transaction termination CONTEXT: PL/pgSQL function b() line 1 at COMMIT SQL function "a" statement 1
As it is, transaction control inside PostgreSQL procedures is somewhat limited.
If you violate any of these rules, you will get the error message you describe in your question. You will probably have to handle transactions in the application rather than in the procedure — perhaps splitting the procedure into smaller parts makes this possible.
answered Dec 3, 2019 at 14:37
Laurenz AlbeLaurenz Albe
192k17 gold badges177 silver badges233 bronze badges
1
Содержание
- Transaction management in PostgreSQL and what is different from Oracle
- TL;DR: Everything is different about transaction behavior. This may also change your ideas about “database independent” applications.
- Auto commit
- Commit on exit
- DDL auto-commit
- Start transaction
- Set Transaction name
- Autonomous Transaction
- User Call level rollback
- ERROR: current transaction is aborted
- Savepoint
- PostgreSQL 11 Procedures
- Invalid transaction termination
- Implicit transactions
- Postgresql error invalid transaction termination
- Submit correction
Transaction management in PostgreSQL and what is different from Oracle
TL;DR: Everything is different about transaction behavior. This may also change your ideas about “database independent” applications.
I like to explain some PostgreSQL concepts from an oracle DBA point of view. There are many things that are different in the two RDBMS and it is important to understand them.
Auto commit
Here is a short example where I create a table, insert one row and rollback:
I never executed any commit but my changes are saved and published. This is auto-commit by default in psql. I think I prefer the Oracle default where the commit is my decision, and only what I’ve committed is visible:
So, is Oracle the best for transaction management?
Well… autocommit default is only one thing. Let’s go further.
First, this is only a client setting. SQL*Plus has it set to off:
But JDBC drivers may have it set to on. So, rather than a nice thing about the database, it is just a nice default of SQL*Plus.
Commit on exit
And wait… are all sqlplus defaults so smart? Let’s come back to my Oracle database where I committed nothing:
Are sqlplus defaults so smart? I didn’t commit the last insert but it was committed by default. Not because of auto-commit but because of exit-commit:
That’s not a nice default. If I quit without saying ‘commit’ I want a rollback. It is highly recommended to set exit-commit off to avoid any unexpected commit. (also recommendedvfor scripts having a WHENEVER SQLERROR EXIT that mentions ROLLBACK because COMMIT is the default).
DDL auto-commit
And that’s not all… The first row (n=1) was never explicitly committed. It was inserted with DDL (CREATE TABLE) and DDL are always auto-committed. That’s the Oracle Database, nothing to do with the client: you cannot be transactional with DDL.
Start transaction
Back to PostgreSQL, the default is auto-commit but I have the choice. I can explicitly start a transaction and then I’ll have to explicitly commit it.
See? I have the 3 rows from the first run which were all auto-committed. But now that I explicitly started a transaction, everything was transactional, even the DDL: the DROP TABLE, the CREATE TABLE, the INSERT were explicitly rolled-back. And even the last INSERT was implicitly rolled-back on exit.
Now, who is the winner in transaction management? There’s even more: you can send a multi-statement command to the backend and it will be processed as an implicit transaction.
Note that START TRANSACTION is the ANSI SQL syntax, but PostgreSQL accepts also BEGIN, BEGIN TRANSACTION and BEGIN WORK.
Set Transaction name
Don’t think that there are no “begin transaction” in Oracle. The SET TRANSACTION starts it, in order to define the isolation level, or simply to put a name to the transaction.
This example looks at the transaction address in V$SESSION and V$TRANSACTION
This illustrates when a transaction starts (visible with a TADDR in V$SESSION and a row in V$TRANSACTION): the first INSERT/DELETE/UPDATE/MERGE/SELECT FOR UPDATE or a SET TRANSACTION
Autonomous Transaction
Not available in PostgreSQL but possible in Oracle: we can have nested transactions. This is very convenient in some limited cases, like logging the error in the database (and commit this insert) before the rollback of the transaction.
This shows that there can be multiple transactions for the same session. The PADDR is only the address or the top-level one.
User Call level rollback
(Note that I’ve previously written “statement-level rollback” but this is misleading because a statement can be executed in a recursive call, as in a PL/SQL block. What I show here is about a user call statement which can actually be a SQL statement or a PL/SQL block or a procedure call ).
Still in Oracle, when a statement fails in a user call, the modifications done by this user call are rolled back, but not the previous modifications. The transaction can continue (like re-try, or do an alternative change):
ERROR: current transaction is aborted
That’s different in PostgreSQL where the transaction cannot continue when you encounter an error:
Here I rolled back. But I can also commit to terminate the transaction, but it will rollback anyway:
Savepoint
Actually, we can achieve user call level rollback even in PostgreSQL, using savepoints:
With Oracle, you don’t need to because there is an implicit savepoint before each execution.
Well, the PostgreSQL client psql do the same:
and then psql will automatically create a “pg_psql_temporary_savepoint”.
PostgreSQL 11 Procedures
Before version 11 all commands (which can be one statement, or multiple ones, or PL/pgSQL anonymous or stored procedures) were run in an atomic context, with no intermediate commits possible without a roundtrip with the client. Like this:
The first call has inserted values 10 and 12 and they were committed because I run in the default AUTOCOMMIT. But the second call has encountered a duplicate key and the whole was rolled-back. But in PostgreSQL 11 I can add an intermediate commit so that the first rows are committed before I encounter the error.
Here the rows 5 to 9 have been committed before encountering the exception for row 10.
Still, no need for an explicit BEGIN here. The COMMIT in the loop will end the transaction (started implicitly on the server as I did no BEGIN before) and start a new one. The last transaction started will be committed implicitly.
Additionally, you can look at the timestamps in the two previous demos. In PostgreSQL, current_timestamp is consistent in the transaction.
Invalid transaction termination
Here is the same, but with AUTOCOMMIT off:
Actually, this is what made me start this blog post. But that’s already a lot, and further investigation on this will be on the next post:
AUTOCOMMIT and PostgreSQL transaction management in procedures
Implicit transactions
Ok, just one more on this. The error above is not really caused by AUTOCOMMIT but by the way psql handles AUTOCOMMIT. Actually, with PostgreSQL, the AUTOCOMMIT is not a BEGIN statement added by the psql client, but the backend server creating an implicit transaction when there is not already one. It is even the opposite: when AUTOCOMMIT is off, the client adds a “BEGIN” so that the server does not create an implicit one. Which means that the “invalid transaction termination” occurs also without auto-commit when we CALL the procedure from an explicit transaction.
This long post is only a short sample of the many differences between Oracle and PostgreSQL transaction management. And I’ve only used psql and sqlplus here. Do you want to test the same from Java JDBC and Python psycopg2?
Источник
Postgresql error invalid transaction termination
All messages emitted by the PostgreSQL server are assigned five-character error codes that follow the SQL standard’s conventions for “ SQLSTATE ” codes. Applications that need to know which error condition has occurred should usually test the error code, rather than looking at the textual error message. The error codes are less likely to change across PostgreSQL releases, and also are not subject to change due to localization of error messages. Note that some, but not all, of the error codes produced by PostgreSQL are defined by the SQL standard; some additional error codes for conditions not defined by the standard have been invented or borrowed from other databases.
According to the standard, the first two characters of an error code denote a class of errors, while the last three characters indicate a specific condition within that class. Thus, an application that does not recognize the specific error code might still be able to infer what to do from the error class.
Table A.1 lists all the error codes defined in PostgreSQL 15.1. (Some are not actually used at present, but are defined by the SQL standard.) The error classes are also shown. For each error class there is a “ standard ” error code having the last three characters 000 . This code is used only for error conditions that fall within the class but do not have any more-specific code assigned.
The symbol shown in the column “ Condition Name ” is the condition name to use in PL/pgSQL . Condition names can be written in either upper or lower case. (Note that PL/pgSQL does not recognize warning, as opposed to error, condition names; those are classes 00, 01, and 02.)
For some types of errors, the server reports the name of a database object (a table, table column, data type, or constraint) associated with the error; for example, the name of the unique constraint that caused a unique_violation error. Such names are supplied in separate fields of the error report message so that applications need not try to extract them from the possibly-localized human-readable text of the message. As of PostgreSQL 9.3, complete coverage for this feature exists only for errors in SQLSTATE class 23 (integrity constraint violation), but this is likely to be expanded in future.
Table A.1. PostgreSQL Error Codes
Error Code | Condition Name |
---|---|
Class 00 — Successful Completion | |
00000 | successful_completion |
Class 01 — Warning | |
01000 | warning |
0100C | dynamic_result_sets_returned |
01008 | implicit_zero_bit_padding |
01003 | null_value_eliminated_in_set_function |
01007 | privilege_not_granted |
01006 | privilege_not_revoked |
01004 | string_data_right_truncation |
01P01 | deprecated_feature |
Class 02 — No Data (this is also a warning class per the SQL standard) | |
02000 | no_data |
02001 | no_additional_dynamic_result_sets_returned |
Class 03 — SQL Statement Not Yet Complete | |
03000 | sql_statement_not_yet_complete |
Class 08 — Connection Exception | |
08000 | connection_exception |
08003 | connection_does_not_exist |
08006 | connection_failure |
08001 | sqlclient_unable_to_establish_sqlconnection |
08004 | sqlserver_rejected_establishment_of_sqlconnection |
08007 | transaction_resolution_unknown |
08P01 | protocol_violation |
Class 09 — Triggered Action Exception | |
09000 | triggered_action_exception |
Class 0A — Feature Not Supported | |
0A000 | feature_not_supported |
Class 0B — Invalid Transaction Initiation | |
0B000 | invalid_transaction_initiation |
Class 0F — Locator Exception | |
0F000 | locator_exception |
0F001 | invalid_locator_specification |
Class 0L — Invalid Grantor | |
0L000 | invalid_grantor |
0LP01 | invalid_grant_operation |
Class 0P — Invalid Role Specification | |
0P000 | invalid_role_specification |
Class 0Z — Diagnostics Exception | |
0Z000 | diagnostics_exception |
0Z002 | stacked_diagnostics_accessed_without_active_handler |
Class 20 — Case Not Found | |
20000 | case_not_found |
Class 21 — Cardinality Violation | |
21000 | cardinality_violation |
Class 22 — Data Exception | |
22000 | data_exception |
2202E | array_subscript_error |
22021 | character_not_in_repertoire |
22008 | datetime_field_overflow |
22012 | division_by_zero |
22005 | error_in_assignment |
2200B | escape_character_conflict |
22022 | indicator_overflow |
22015 | interval_field_overflow |
2201E | invalid_argument_for_logarithm |
22014 | invalid_argument_for_ntile_function |
22016 | invalid_argument_for_nth_value_function |
2201F | invalid_argument_for_power_function |
2201G | invalid_argument_for_width_bucket_function |
22018 | invalid_character_value_for_cast |
22007 | invalid_datetime_format |
22019 | invalid_escape_character |
2200D | invalid_escape_octet |
22025 | invalid_escape_sequence |
22P06 | nonstandard_use_of_escape_character |
22010 | invalid_indicator_parameter_value |
22023 | invalid_parameter_value |
22013 | invalid_preceding_or_following_size |
2201B | invalid_regular_expression |
2201W | invalid_row_count_in_limit_clause |
2201X | invalid_row_count_in_result_offset_clause |
2202H | invalid_tablesample_argument |
2202G | invalid_tablesample_repeat |
22009 | invalid_time_zone_displacement_value |
2200C | invalid_use_of_escape_character |
2200G | most_specific_type_mismatch |
22004 | null_value_not_allowed |
22002 | null_value_no_indicator_parameter |
22003 | numeric_value_out_of_range |
2200H | sequence_generator_limit_exceeded |
22026 | string_data_length_mismatch |
22001 | string_data_right_truncation |
22011 | substring_error |
22027 | trim_error |
22024 | unterminated_c_string |
2200F | zero_length_character_string |
22P01 | floating_point_exception |
22P02 | invalid_text_representation |
22P03 | invalid_binary_representation |
22P04 | bad_copy_file_format |
22P05 | untranslatable_character |
2200L | not_an_xml_document |
2200M | invalid_xml_document |
2200N | invalid_xml_content |
2200S | invalid_xml_comment |
2200T | invalid_xml_processing_instruction |
22030 | duplicate_json_object_key_value |
22031 | invalid_argument_for_sql_json_datetime_function |
22032 | invalid_json_text |
22033 | invalid_sql_json_subscript |
22034 | more_than_one_sql_json_item |
22035 | no_sql_json_item |
22036 | non_numeric_sql_json_item |
22037 | non_unique_keys_in_a_json_object |
22038 | singleton_sql_json_item_required |
22039 | sql_json_array_not_found |
2203A | sql_json_member_not_found |
2203B | sql_json_number_not_found |
2203C | sql_json_object_not_found |
2203D | too_many_json_array_elements |
2203E | too_many_json_object_members |
2203F | sql_json_scalar_required |
2203G | sql_json_item_cannot_be_cast_to_target_type |
Class 23 — Integrity Constraint Violation | |
23000 | integrity_constraint_violation |
23001 | restrict_violation |
23502 | not_null_violation |
23503 | foreign_key_violation |
23505 | unique_violation |
23514 | check_violation |
23P01 | exclusion_violation |
Class 24 — Invalid Cursor State | |
24000 | invalid_cursor_state |
Class 25 — Invalid Transaction State | |
25000 | invalid_transaction_state |
25001 | active_sql_transaction |
25002 | branch_transaction_already_active |
25008 | held_cursor_requires_same_isolation_level |
25003 | inappropriate_access_mode_for_branch_transaction |
25004 | inappropriate_isolation_level_for_branch_transaction |
25005 | no_active_sql_transaction_for_branch_transaction |
25006 | read_only_sql_transaction |
25007 | schema_and_data_statement_mixing_not_supported |
25P01 | no_active_sql_transaction |
25P02 | in_failed_sql_transaction |
25P03 | idle_in_transaction_session_timeout |
Class 26 — Invalid SQL Statement Name | |
26000 | invalid_sql_statement_name |
Class 27 — Triggered Data Change Violation | |
27000 | triggered_data_change_violation |
Class 28 — Invalid Authorization Specification | |
28000 | invalid_authorization_specification |
28P01 | invalid_password |
Class 2B — Dependent Privilege Descriptors Still Exist | |
2B000 | dependent_privilege_descriptors_still_exist |
2BP01 | dependent_objects_still_exist |
Class 2D — Invalid Transaction Termination | |
2D000 | invalid_transaction_termination |
Class 2F — SQL Routine Exception | |
2F000 | sql_routine_exception |
2F005 | function_executed_no_return_statement |
2F002 | modifying_sql_data_not_permitted |
2F003 | prohibited_sql_statement_attempted |
2F004 | reading_sql_data_not_permitted |
Class 34 — Invalid Cursor Name | |
34000 | invalid_cursor_name |
Class 38 — External Routine Exception | |
38000 | external_routine_exception |
38001 | containing_sql_not_permitted |
38002 | modifying_sql_data_not_permitted |
38003 | prohibited_sql_statement_attempted |
38004 | reading_sql_data_not_permitted |
Class 39 — External Routine Invocation Exception | |
39000 | external_routine_invocation_exception |
39001 | invalid_sqlstate_returned |
39004 | null_value_not_allowed |
39P01 | trigger_protocol_violated |
39P02 | srf_protocol_violated |
39P03 | event_trigger_protocol_violated |
Class 3B — Savepoint Exception | |
3B000 | savepoint_exception |
3B001 | invalid_savepoint_specification |
Class 3D — Invalid Catalog Name | |
3D000 | invalid_catalog_name |
Class 3F — Invalid Schema Name | |
3F000 | invalid_schema_name |
Class 40 — Transaction Rollback | |
40000 | transaction_rollback |
40002 | transaction_integrity_constraint_violation |
40001 | serialization_failure |
40003 | statement_completion_unknown |
40P01 | deadlock_detected |
Class 42 — Syntax Error or Access Rule Violation | |
42000 | syntax_error_or_access_rule_violation |
42601 | syntax_error |
42501 | insufficient_privilege |
42846 | cannot_coerce |
42803 | grouping_error |
42P20 | windowing_error |
42P19 | invalid_recursion |
42830 | invalid_foreign_key |
42602 | invalid_name |
42622 | name_too_long |
42939 | reserved_name |
42804 | datatype_mismatch |
42P18 | indeterminate_datatype |
42P21 | collation_mismatch |
42P22 | indeterminate_collation |
42809 | wrong_object_type |
428C9 | generated_always |
42703 | undefined_column |
42883 | undefined_function |
42P01 | undefined_table |
42P02 | undefined_parameter |
42704 | undefined_object |
42701 | duplicate_column |
42P03 | duplicate_cursor |
42P04 | duplicate_database |
42723 | duplicate_function |
42P05 | duplicate_prepared_statement |
42P06 | duplicate_schema |
42P07 | duplicate_table |
42712 | duplicate_alias |
42710 | duplicate_object |
42702 | ambiguous_column |
42725 | ambiguous_function |
42P08 | ambiguous_parameter |
42P09 | ambiguous_alias |
42P10 | invalid_column_reference |
42611 | invalid_column_definition |
42P11 | invalid_cursor_definition |
42P12 | invalid_database_definition |
42P13 | invalid_function_definition |
42P14 | invalid_prepared_statement_definition |
42P15 | invalid_schema_definition |
42P16 | invalid_table_definition |
42P17 | invalid_object_definition |
Class 44 — WITH CHECK OPTION Violation | |
44000 | with_check_option_violation |
Class 53 — Insufficient Resources | |
53000 | insufficient_resources |
53100 | disk_full |
53200 | out_of_memory |
53300 | too_many_connections |
53400 | configuration_limit_exceeded |
Class 54 — Program Limit Exceeded | |
54000 | program_limit_exceeded |
54001 | statement_too_complex |
54011 | too_many_columns |
54023 | too_many_arguments |
Class 55 — Object Not In Prerequisite State | |
55000 | object_not_in_prerequisite_state |
55006 | object_in_use |
55P02 | cant_change_runtime_param |
55P03 | lock_not_available |
55P04 | unsafe_new_enum_value_usage |
Class 57 — Operator Intervention | |
57000 | operator_intervention |
57014 | query_canceled |
57P01 | admin_shutdown |
57P02 | crash_shutdown |
57P03 | cannot_connect_now |
57P04 | database_dropped |
57P05 | idle_session_timeout |
Class 58 — System Error (errors external to PostgreSQL itself) | |
58000 | system_error |
58030 | io_error |
58P01 | undefined_file |
58P02 | duplicate_file |
Class 72 — Snapshot Failure | |
72000 | snapshot_too_old |
Class F0 — Configuration File Error | |
F0000 | config_file_error |
F0001 | lock_file_exists |
Class HV — Foreign Data Wrapper Error (SQL/MED) | |
HV000 | fdw_error |
HV005 | fdw_column_name_not_found |
HV002 | fdw_dynamic_parameter_value_needed |
HV010 | fdw_function_sequence_error |
HV021 | fdw_inconsistent_descriptor_information |
HV024 | fdw_invalid_attribute_value |
HV007 | fdw_invalid_column_name |
HV008 | fdw_invalid_column_number |
HV004 | fdw_invalid_data_type |
HV006 | fdw_invalid_data_type_descriptors |
HV091 | fdw_invalid_descriptor_field_identifier |
HV00B | fdw_invalid_handle |
HV00C | fdw_invalid_option_index |
HV00D | fdw_invalid_option_name |
HV090 | fdw_invalid_string_length_or_buffer_length |
HV00A | fdw_invalid_string_format |
HV009 | fdw_invalid_use_of_null_pointer |
HV014 | fdw_too_many_handles |
HV001 | fdw_out_of_memory |
HV00P | fdw_no_schemas |
HV00J | fdw_option_name_not_found |
HV00K | fdw_reply_handle |
HV00Q | fdw_schema_not_found |
HV00R | fdw_table_not_found |
HV00L | fdw_unable_to_create_execution |
HV00M | fdw_unable_to_create_reply |
HV00N | fdw_unable_to_establish_connection |
Class P0 — PL/pgSQL Error | |
P0000 | plpgsql_error |
P0001 | raise_exception |
P0002 | no_data_found |
P0003 | too_many_rows |
P0004 | assert_failure |
Class XX — Internal Error | |
XX000 | internal_error |
XX001 | data_corrupted |
XX002 | index_corrupted |
Prev | Up | Next |
Part VIII. Appendixes | Home | Appendix B. Date/Time Support |
Submit correction
If you see anything in the documentation that is not correct, does not match your experience with the particular feature or requires further clarification, please use this form to report a documentation issue.
Источник
I’m trying to learn PostgreSQL stored procedures. Specifically creating the procedure below in PSQL.
CREATE OR REPLACE PROCEDURE BUILD_AND_POPULATE(INOUT cresults refcursor)
LANGUAGE PLPGSQL
AS $$
BEGIN
BEGIN; -- I've tried removing this but the behaviour is the same
cresults:= 'cur';
DROP TABLE IF EXISTS procsampledata;
CREATE TABLE procsampledata as select x,1 as c2,2 as c3, md5(random()::text) from generate_series(1,10) x;
COMMIT;
OPEN cresults FOR SELECT * FROM procsampledata;
END;
$$;
Then I execute it like so, but receive an error:
postgres=# call build_and_populate(null);
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function build_and_populate(refcursor) line 6 at COMMIT
I’ve tried with AUTOCOMMIT set to both on and off.
This is my Postgres version
PostgreSQL 11.5 on x86_64-pc-linux-musl, compiled by gcc (Alpine 8.3.0) 8.3.0, 64-bit
Can anyone see what I’m doing wrong? Thank you!
People have blogged about the fact that commit
is legal in a PostgreSQL stored proc, starting with version 11—which is what yugabyteDB Version 1.3.0 uses. Here’s an example:
However, these blogs forget that psql
(and therefore ysqlsh
) have AUTOCOMMIT
set to True
by default. They seem to do their tests in that mode and forget that the AUTOCOMMIT
mode might be False
. However, things change when the AUTOCOMMIT
mode is False
. See the following testcase.
I would like to hear the explanation for this weird behavior. It’s add odds with the definition of autocommitting: if the insert has autocommitted, then by definition there’s nothing to commit
or rollback
.
Following on from this, I see no reason why call my_proc()
has to start a txn. It might do no more than a calculation. As I see it, it’s the first SQL statement that’s executed from the stored proc that should start a txn. Notice that within the present regime, and given that the weakest isolation level in yugabyte DB is repeatable read
, you cannot implement a stored proc to watch a table in an infinite loop and to respond when it suffers an interesting change. Unless, that is, the non-intuitive workaround that I show below helps there.
Note: this remains to be tested.
i psqlrc
set AUTOCOMMIT False
begin;
drop table if exists marbles;
commit;
begin;
create table marbles(
pk integer
constraint t_pk primary key,
color text
constraint t_c1_nn not null
constraint t_c1_chk check(color in ('black', 'white'))
);
commit;
begin;
insert into marbles(pk, color) values
(1, 'black'),
(2, 'white');
commit;
begin;
drop procedure if exists set_all_marbles_black();
commit;
begin;
create procedure set_all_marbles_black()
language plpgsql
as $$
begin
-- The "start transaction" and "set transaction" errors can be
-- understood in the context that "call" starts a transaction, if
-- one isn't already ongoing.
-- Commented out 'cos causes run-time error
-- "0A000: unsupported transaction command in PL/pgSQL".
-- start transaction;
-- Commented out 'cos causes run-time error
-- "25001: SET TRANSACTION ISOLATION LEVEL must be called before any query"
-- Yet, as a top-level command, it *does* start a txn.
-- set transaction isolation level serializable;
update marbles set color = 'black' where color = 'white';
-- It's hard to understand the thinking that requires this error.
-- Especially given that it occurs when AUTOCOMMIT is False
-- but not when it's True.
-- Causes "2D000: invalid transaction termination"
rollback;
end;
$$;
commit;
select pk, color from marbles order by pk;
rollback;
set AUTOCOMMIT True
-- Runs OK, and the "rollback" has its effect.
call set_all_marbles_black();
select pk, color from marbles order by pk;
set AUTOCOMMIT False
-- This causes:
-- ysql:plpgsql_proc_txn_control.sql:63: ERROR: invalid transaction termination
-- CONTEXT: PL/pgSQL function set_all_marbles_black() line 21 at ROLLBACK
call set_all_marbles_black();
-- Must rollback before trying anything else.
rollback;
Here, for completeness, is another manifestation of the same issue. This quietly succeeds:
set AUTOCOMMIT True
do $$
begin
update marbles set color = 'black' where color = 'white';
commit;
end
$$;
But this fails with "ERROR: invalid transaction termination"
:
set AUTOCOMMIT False
do $$
begin
update marbles set color = 'black' where color = 'white';
commit;
end
$$;
Hi Jagmohan,
What is the point of COMMITting after a few records? Why not let the
whole batch run through. If there are any errors, PostgreSQL will
rollback the whole transaction anyway.
As opposed to Oracle, PostgreSQL won’t commit some records, while others
failed within the same transaction. As soon as a single operation within
a transaction fails, it’s dead and all subsequent operations will fail.
Regards,
Holger
Am 23.11.20 um 18:05 schrieb Jagmohan Kaintura:
> Hi Team,
>
> We have many BATCH JOBS in Oracle which we are committing after
> processing few Records. These batch Jobs process in some subsets and
> call transaction control statements COMMIT in case of Success and
> ROLLBACK in case of failure.
>
> While converting to POstgreSQL we converted in Same Format with COMMIT
> and ROLLBACK. But while executing it ended up with below error message.
> ERROR: invalid transaction termination
> CONTEXT: PL/pgSQL function inline_code_block line 29 at COMMIT
>
> While reviewing the Transaction Management in PostgreSQL
> «https://www.postgresql.org/docs/12/plpgsql-transactions.html
> <https://www.postgresql.org/docs/12/plpgsql-transactions.html>» it
> speaks about a format which is not Supported.
>
> Transaction control is only possible in |CALL| or |DO| invocations
> from the top level or nested |CALL| or |DO| invocations without any
> other intervening command. For example, if the call stack is |CALL
> proc1()| → |CALL proc2()| → |CALL proc3()|, then the second and third
> procedures can perform transaction control actions. But if the call
> stack is |CALL proc1()| → |SELECT func2()| → |CALL proc3()|, then the
> last procedure cannot do transaction control, because of the
> |SELECT| in between.
>
> My Call has : CALL Batch Job => SELECT function Used in SQL
> Statements ==> Call Procedure. We have transaction control in «CALL
> Batch Job» only.
>
> Pseudo Code is like : Highlighted in BOLD is a function call. It’s
> failing when getting executed as we are using functions into this
> procedure.
> *Can any help on this matter , how I can implement Batch Jobs as we
> wanted to commit in few intervals of 20000 records ?*
> We can’t remove this function from the statement as its value is
> dependent on column value.
>
> CREATE OR REPLACE PROCEDURE TEST_TRANSACTION(
> )
> LANGUAGE ‘plpgsql’
> SECURITY DEFINER
> AS $BODY$
> DECLARE
> G_LAST_UPDATE_USER_SYSTEM VARCHAR2(6) := ‘SYSTEM’;
> G_LAST_UPDATE_MODULE_BATCH VARCHAR2(5) := ‘BATCH’;
>
> G_CNTR_LOADING_EXPIRED TMS_CONTAINER_LOADING.STATUS_CODE%TYPE := ’90’;
> G_DG_MANIFEST_DELETED TMS_DG_MANIFEST.STATUS_CODE%TYPE := ’80’;
> G_DG_MANIFEST_COMPLETED TMS_DG_MANIFEST.STATUS_CODE%TYPE := ’95’;
>
> v_num_day numeric;
> v_batch_count numeric;
> v_log_count numeric := 0;
> v_local_batch_count numeric;
> BEGIN
> v_batch_count := 0;
> LOOP
> update tms_container_loading
> set status_code = G_CNTR_LOADING_EXPIRED
> , last_update_tm = clock_timestamp()::timestamp(0)
> , last_update_user_an = G_LAST_UPDATE_USER_SYSTEM
> , last_update_module_code = G_LAST_UPDATE_MODULE_BATCH
> where
> *tms$vvd.is_vessel_departed(vessel_visit_c,trunc(clock_timestamp()::timestamp(0)))* =
> 1
> and coalesce(status_code,’~’) <> G_CNTR_LOADING_EXPIRED
> and ctid in (select a.ctid from tms_container_loading where
> *tms$vvd.is_vessel_departed(vessel_visit_c,trunc(clock_timestamp()::timestamp(0)))* =
> 1
> and coalesce(status_code,’~’) <> G_CNTR_LOADING_EXPIRED
> LIMIT 20000);
> EXIT WHEN NOT FOUND; /* apply on SQL */
> GET DIAGNOSTICS v_local_batch_count = ROW_COUNT;
> v_batch_count := v_batch_count + v_local_batch_count;
> COMMIT;
> END LOOP;
> v_log_count := v_log_count + 1; CALL
> Log(v_batch_count,’TMS_CONTAINER_LOADING’,NULL, ‘TMS$BATCH_JOB’,
> v_log_count);
> COMMIT;
> END;
> $BODY$;
>
> *Best Regards,*
> Jagmohan
—
Holger Jakobs, Bergisch Gladbach, Tel. +49-178-9759012
Studying Stored Procs in Postgres 11
With Postgres 11 looming on the near horizon, it’s only appropriate to check out a recent beta and kick the tires a few times. Whether it’s improvements in parallelism, partitions, stored procedures, JIT functionality, or any number of elements in the release page, there’s a lot to investigate.
It just so happens that I ran across a fortuitous event on Twitter when deciding on an appropriate topic. Behold!
Wasting XIDs has never been this fun
Wait! No! That’s not what stored procedures are for!
I felt so good like anything was possible
When confronted with such a blatant corruption of such a cool new feature, it’s only natural to question the wisdom of doing so. It is, after all, not a great idea to programatically consume transaction IDs. I said as much and moved on with life, certain the worst was over.
Then this happened.
Magnus helpfully recommends proceeding
Now, Magnus is infamous for two things: his technical acumen, and giddy malevolence. His advocacy of a stored procedure named “waste_xid” only proved nobody anywhere should ever run this anywhere, lest they immolate whatever system hosted the database instance.
But hey, VMs are cheap; let’s break things. How else can we learn the limits of our new toys, but by virtually atomizing them?
I hit cruise control and rubbed my eyes
Before we saddle our poor innocent Postgres 11 installation with an inadvisable stored procedure designed specifically to underhandedly reap its transaction lifespan, we should probably make the routine as evil as possible.
One thing stands out immediately: calling EXECUTE
is unnecessary overhead. According to the information function documentation, txid_current
will assign a new transaction ID if there isn’t one already. Since the stored procedure is constantly committing, that’s extremely handy. And since this is Pl/pgSQL, we can use direct assignment instead.
Our new procedure looks something like this:
CREATE OR REPLACE PROCEDURE waste_xid(cnt int) AS $$ DECLARE i INT; x BIGINT; BEGIN FOR i in 1..cnt LOOP x := txid_current(); COMMIT; END LOOP; END; $$ LANGUAGE plpgsql;
Great! We can now waste XIDs about 4x faster than before! On this particular test VM, the maximum amount of wasted XIDs per second was about 125k. At that rate, we could blow through a billion in roughly two hours.
Since quite a few installations use the default autovacuum settings, that means we could trigger a lot of vacuums at the 200M transaction mark in about half an hour. We didn’t need that disk throughput anyway.
Workin’ on a mystery, goin’ wherever it leads
Regardless, what happens if we run it?
CALL waste_xid(1000000); CALL
Well, that was anticlimactic. How about the logs? We set our min_log_duration_statement
to 100ms for debugging purposes, so we should definitely see a long call there.
2018-10-03 20:53:02.505 UTC [11334] [email protected] LOG: duration: 7949.540 ms statement: call waste_xid(1000000);
Here’s something that’s actually more interesting than it might appear at first glance. Stored procedures, it would seem, are atomic. We tested explicitly by adding a long pg_sleep
call in the loop, and none of those entries were logged.
Since procedures can COMMIT
transactions and thus could contain semantically unrelated unrelated activity to the originating transaction, can a procedure essentially derail an existing transaction? Let’s see:
BEGIN; CALL waste_xid(1000000); ERROR: invalid transaction termination CONTEXT: PL/pgSQL function waste_xid(integer) line 8 at COMMIT
Nope! Apparently CALL
is only valid within its own context; no transactions for you. This could introduce interesting behavior in application layers that implicitly start a transaction before every command. The resulting error is a bit ambiguous however; why is the transaction termination invalid? Can’t it just say we can’t close the current transaction from within a procedure?
So stored procedures are atomic, and can do whatever they want in their own transactional context. What else are they hiding?
I put the pedal down to make some time
It seems there are special rules to how transactions are handled in stored procedures. While the discussion was hot, a couple more functional variants popped up. Here’s an interesting one:
CREATE OR REPLACE PROCEDURE check_now() AS $$ DECLARE i int; BEGIN FOR i in 1..5 LOOP RAISE NOTICE 'It is now: %', now(); PERFORM txid_current(); COMMIT; PERFORM pg_sleep(0.1); END LOOP; END; $$ LANGUAGE plpgsql;
What are we testing for here? Well, one fundamental attribute of the now()
function and several other related DATETIME routines, is that they are tied to the current transaction. So in theory, the value being returned should change.
Let’s try it:
CALL check_now(); NOTICE: It is now: 2018-10-03 21:27:07.764822+00 NOTICE: It is now: 2018-10-03 21:27:07.764822+00 NOTICE: It is now: 2018-10-03 21:27:07.764822+00 NOTICE: It is now: 2018-10-03 21:27:07.764822+00 NOTICE: It is now: 2018-10-03 21:27:07.764822+00
Uh oh. What does this actually mean? Is now()
being tethered to the initial CALL
instead of the current transaction? Or are PL/pgSQL transactions not real transactions? If they do operate in a different context than the outer transaction, how else does that context diverge? Or is this a bug?
Postgres 11 is still in beta, after all.
It was always cold, no sunshine
Upon adopting the PROCEDURE
nomenclature, we’re also presented with a rather unique historical quirk. Consider the previous syntax for creating a trigger. More than a few triggers have been declared this way:
CREATE TRIGGER t_my_trigger BEFORE INSERT OR UPDATE ON mytable FOR EACH ROW EXECUTE PROCEDURE my_function();
Ever prescient, the devs saw this coming and added a disclaimer to the Postgres 11 docs:
In the syntax of CREATE TRIGGER, the keywords FUNCTION and PROCEDURE are equivalent, but the referenced function must in any case be a function, not a procedure. The use of the keyword PROCEDURE here is historical and deprecated.
So in case you’d mentally linked the historical—and up until Postgres 11, current—syntax with the new stored procedure functionality, don’t. Procedures really did change more of the Postgres internals than a cursory examination could reveal.
There’s something good waitin’ down this road
Now that we’ve kicked the proverbial tires and explored a few of the neat quirks stored procedures offer, what does it all mean?
First and foremost, the ability to COMMIT
or ROLLBACK
within a function call allows both safeguarding activity batches or particularly critical data. No longer will one function call represent an all-or-nothing proposition. That alone is a massive enhancement to Postgres, one which users of other database engines such as Oracle have been clamoring for over the last 20 years.
More subtly, it presents a new security concern that administrators should consider. The ability to manipulate transactions is a powerful lever ripe for abuse. At minimum, DBAs should consider revoking USAGE
on any languages from users that shouldn’t be granted that capability.
REVOKE USAGE ON LANGUAGE plpgsql FROM PUBLIC;
Users will still be able to invoke previously defined functions or procedures, but won’t be able to create new ones. In fact, this should probably be done on all current systems as a matter of course.
Judiciously wielded, Postgres can continue runnin’ down the dream of becoming the best and most feature-laden database. We just need to pay attention to where we’re going.
Содержание
- Обсуждение: ERROR : invalid transaction termination : PostgreSQL v12
- ERROR : invalid transaction termination : PostgreSQL v12
- Re: ERROR : invalid transaction termination : PostgreSQL v12
- Недействительное завершение транзакции
- 1 ответ
- Thread: ERROR : invalid transaction termination : PostgreSQL v12
- ERROR : invalid transaction termination : PostgreSQL v12
- Re: ERROR : invalid transaction termination : PostgreSQL v12
- Please explain the inscrutable rules for when «commit» in a stored proc succeeds or causes a run-time error #1957
- Comments
- bllewell commented Aug 2, 2019 •
- Недействительное завершение транзакции
- 1 ответ
Обсуждение: ERROR : invalid transaction termination : PostgreSQL v12
ERROR : invalid transaction termination : PostgreSQL v12
CREATE OR REPLACE PROCEDURE TEST_TRANSACTION(
)
LANGUAGE ‘plpgsql’
SECURITY DEFINER
AS $BODY$
DECLARE
G_LAST_UPDATE_USER_SYSTEM VARCHAR2(6) := ‘SYSTEM’;
G_LAST_UPDATE_MODULE_BATCH VARCHAR2(5) := ‘BATCH’;
G_CNTR_LOADING_EXPIRED TMS_CONTAINER_LOADING.STATUS_CODE%TYPE := ’90’;
G_DG_MANIFEST_DELETED TMS_DG_MANIFEST.STATUS_CODE%TYPE := ’80’;
G_DG_MANIFEST_COMPLETED TMS_DG_MANIFEST.STATUS_CODE%TYPE := ’95’;
v_num_day numeric;
v_batch_count numeric;
v_log_count numeric := 0;
v_local_batch_count numeric;
BEGIN
v_batch_count := 0;
LOOP
update tms_container_loading
set status_code = G_CNTR_LOADING_EXPIRED
, last_update_tm = clock_timestamp()::timestamp(0)
, last_update_user_an = G_LAST_UPDATE_USER_SYSTEM
, last_update_module_code = G_LAST_UPDATE_MODULE_BATCH
where tms$vvd.is_vessel_departed(vessel_visit_c,trunc(clock_timestamp()::timestamp(0))) = 1
and coalesce(status_code,’
‘) <> G_CNTR_LOADING_EXPIRED and ctid in (select a.ctid from tms_container_loading where tms$vvd.is_vessel_departed(vessel_visit_c,trunc(clock_timestamp()::timestamp(0))) = 1
and coalesce(status_code,’
‘) <> G_CNTR_LOADING_EXPIRED LIMIT 20000);
EXIT WHEN NOT FOUND; /* apply on SQL */
GET DIAGNOSTICS v_local_batch_count = ROW_COUNT; v_batch_count := v_batch_count + v_local_batch_count;
COMMIT;
END LOOP;
v_log_count := v_log_count + 1; CALL Log(v_batch_count,’TMS_CONTAINER_LOADING’,NULL, ‘TMS$BATCH_JOB’, v_log_count);
COMMIT;
END;
$BODY$;
Re: ERROR : invalid transaction termination : PostgreSQL v12
What is the point of COMMITting after a few records? Why not let the whole batch run through. If there are any errors, PostgreSQL will rollback the whole transaction anyway.
As opposed to Oracle, PostgreSQL won’t commit some records, while others failed within the same transaction. As soon as a single operation within a transaction fails, it’s dead and all subsequent operations will fail.
CREATE OR REPLACE PROCEDURE TEST_TRANSACTION(
)
LANGUAGE ‘plpgsql’
SECURITY DEFINER
AS $BODY$
DECLARE
G_LAST_UPDATE_USER_SYSTEM VARCHAR2(6) := ‘SYSTEM’;
G_LAST_UPDATE_MODULE_BATCH VARCHAR2(5) := ‘BATCH’;
G_CNTR_LOADING_EXPIRED TMS_CONTAINER_LOADING.STATUS_CODE%TYPE := ’90’;
G_DG_MANIFEST_DELETED TMS_DG_MANIFEST.STATUS_CODE%TYPE := ’80’;
G_DG_MANIFEST_COMPLETED TMS_DG_MANIFEST.STATUS_CODE%TYPE := ’95’;
v_num_day numeric;
v_batch_count numeric;
v_log_count numeric := 0;
v_local_batch_count numeric;
BEGIN
v_batch_count := 0;
LOOP
update tms_container_loading
set status_code = G_CNTR_LOADING_EXPIRED
, last_update_tm = clock_timestamp()::timestamp(0)
, last_update_user_an = G_LAST_UPDATE_USER_SYSTEM
, last_update_module_code = G_LAST_UPDATE_MODULE_BATCH
where tms$vvd.is_vessel_departed(vessel_visit_c,trunc(clock_timestamp()::timestamp(0))) = 1
and coalesce(status_code,’
‘) <> G_CNTR_LOADING_EXPIRED and ctid in (select a.ctid from tms_container_loading where tms$vvd.is_vessel_departed(vessel_visit_c,trunc(clock_timestamp()::timestamp(0))) = 1
and coalesce(status_code,’
‘) <> G_CNTR_LOADING_EXPIRED LIMIT 20000);
EXIT WHEN NOT FOUND; /* apply on SQL */
GET DIAGNOSTICS v_local_batch_count = ROW_COUNT; v_batch_count := v_batch_count + v_local_batch_count;
COMMIT;
END LOOP;
v_log_count := v_log_count + 1; CALL Log(v_batch_count,’TMS_CONTAINER_LOADING’,NULL, ‘TMS$BATCH_JOB’, v_log_count);
COMMIT;
END;
$BODY$;
Источник
Недействительное завершение транзакции
У меня есть процедура, которая, когда я выполняю в dbeaver, отлично работает без проблем, однако, когда я вызываю ее из внешней программы, я получаю сообщение об ошибке ниже. Я не хочу копировать / вставлять здесь полную процедуру, потому что она довольно большая и работает в инструменте db. Я просто копирую / вставляю верх и низ. Что могло быть причиной этого?
Процедура:
Ошибка:
1 ответ
Управление транзакциями возможно только в вызовах CALL или DO с верхнего уровня или вложенных вызовах CALL или DO без какой-либо другой промежуточной команды. Например, если стек вызовов CALL proc1() → CALL proc2() → CALL proc3() , то вторая и третья процедуры могут выполнять действия по управлению транзакциями. Но если стек вызовов CALL proc1() → SELECT func2() → CALL proc3() , то последняя процедура не может управлять транзакцией из-за SELECT между ними.
Есть и другие, недокументированные ограничения:
Вы не можете явно начать транзакцию с помощью BEGIN и зафиксировать ее внутри транзакции. Таким образом, следующее не удастся:
Это может быть улучшено в будущих выпусках.
Все процедуры в стеке вызовов должны быть написаны на PL / pgSQL:
Как бы то ни было, контроль транзакций внутри процедур PostgreSQL несколько ограничен.
Если вы нарушите какое-либо из этих правил, вы получите сообщение об ошибке, описанное в вашем вопросе. Вероятно, вам придется обрабатывать транзакции в приложении, а не в процедуре — возможно, разделение процедуры на более мелкие части делает это возможным.
Источник
Thread: ERROR : invalid transaction termination : PostgreSQL v12
ERROR : invalid transaction termination : PostgreSQL v12
CREATE OR REPLACE PROCEDURE TEST_TRANSACTION(
)
LANGUAGE ‘plpgsql’
В В SECURITY DEFINER
AS $BODY$
DECLARE
В В G_LAST_UPDATE_USER_SYSTEM VARCHAR2(6) := ‘SYSTEM’;
В В G_LAST_UPDATE_MODULE_BATCH VARCHAR2(5) := ‘BATCH’;
В В G_CNTR_LOADING_EXPIRED TMS_CONTAINER_LOADING.STATUS_CODE%TYPE := ’90’;
В В G_DG_MANIFEST_DELETED TMS_DG_MANIFEST.STATUS_CODE%TYPE := ’80’;
В В G_DG_MANIFEST_COMPLETED TMS_DG_MANIFEST.STATUS_CODE%TYPE := ’95’;
В В v_num_day numeric;
В В v_batch_count numeric;
В В v_log_count numeric := 0;
В В v_local_batch_count numeric;
BEGIN
В В В В v_batch_count := 0;
В В В В LOOP
В В В В В В update tms_container_loading
В В В В В В В В set status_code = G_CNTR_LOADING_EXPIRED
В В В В В В В В В , last_update_tm = clock_timestamp()::timestamp(0)
В В В В В В В В В , last_update_user_an = G_LAST_UPDATE_USER_SYSTEM
В В В В В В В В В , last_update_module_code = G_LAST_UPDATE_MODULE_BATCH
В В В В В В В whereВ tms$vvd.is_vessel_departed(vessel_visit_c,trunc(clock_timestamp()::timestamp(0)))В = 1
В В В В В В В В and coalesce(status_code,’
‘) <> G_CNTR_LOADING_EXPIRED В and ctid in (select a.ctid from tms_container_loading В whereВ tms$vvd.is_vessel_departed(vessel_visit_c,trunc(clock_timestamp()::timestamp(0)))В = 1
В В В В В В В В and coalesce(status_code,’
‘) <> G_CNTR_LOADING_EXPIRED LIMIT 20000);
В В В В В В EXIT WHEN NOT FOUND; /* apply on SQL */
В В В В В В GET DIAGNOSTICS v_local_batch_count = ROW_COUNT; v_batch_count := v_batch_count + v_local_batch_count;
В В В В В В COMMIT;
В В В В END LOOP;
В В В В v_log_count := v_log_count + 1; CALL Log(v_batch_count,’TMS_CONTAINER_LOADING’,NULL, ‘TMS$BATCH_JOB’, v_log_count);
В В В В COMMIT;
END;
$BODY$;
Re: ERROR : invalid transaction termination : PostgreSQL v12
What is the point of COMMITting after a few records? Why not let the whole batch run through. If there are any errors, PostgreSQL will rollback the whole transaction anyway.
As opposed to Oracle, PostgreSQL won’t commit some records, while others failed within the same transaction. As soon as a single operation within a transaction fails, it’s dead and all subsequent operations will fail.
CREATE OR REPLACE PROCEDURE TEST_TRANSACTION(
)
LANGUAGE ‘plpgsql’
В В SECURITY DEFINER
AS $BODY$
DECLARE
В В G_LAST_UPDATE_USER_SYSTEM VARCHAR2(6) := ‘SYSTEM’;
В В G_LAST_UPDATE_MODULE_BATCH VARCHAR2(5) := ‘BATCH’;
В В G_CNTR_LOADING_EXPIRED TMS_CONTAINER_LOADING.STATUS_CODE%TYPE := ’90’;
В В G_DG_MANIFEST_DELETED TMS_DG_MANIFEST.STATUS_CODE%TYPE := ’80’;
В В G_DG_MANIFEST_COMPLETED TMS_DG_MANIFEST.STATUS_CODE%TYPE := ’95’;
В В v_num_day numeric;
В В v_batch_count numeric;
В В v_log_count numeric := 0;
В В v_local_batch_count numeric;
BEGIN
В В В В v_batch_count := 0;
В В В В LOOP
В В В В В В update tms_container_loading
В В В В В В В В set status_code = G_CNTR_LOADING_EXPIRED
В В В В В В В В В , last_update_tm = clock_timestamp()::timestamp(0)
В В В В В В В В В , last_update_user_an = G_LAST_UPDATE_USER_SYSTEM
В В В В В В В В В , last_update_module_code = G_LAST_UPDATE_MODULE_BATCH
В В В В В В В whereВ tms$vvd.is_vessel_departed(vessel_visit_c,trunc(clock_timestamp()::timestamp(0)))В = 1
В В В В В В В В and coalesce(status_code,’
‘) <> G_CNTR_LOADING_EXPIRED В and ctid in (select a.ctid from tms_container_loading В whereВ tms$vvd.is_vessel_departed(vessel_visit_c,trunc(clock_timestamp()::timestamp(0)))В = 1
В В В В В В В В and coalesce(status_code,’
‘) <> G_CNTR_LOADING_EXPIRED LIMIT 20000);
В В В В В В EXIT WHEN NOT FOUND; /* apply on SQL */
В В В В В В GET DIAGNOSTICS v_local_batch_count = ROW_COUNT; v_batch_count := v_batch_count + v_local_batch_count;
В В В В В В COMMIT;
В В В В END LOOP;
В В В В v_log_count := v_log_count + 1; CALL Log(v_batch_count,’TMS_CONTAINER_LOADING’,NULL, ‘TMS$BATCH_JOB’, v_log_count);
В В В В COMMIT;
END;
$BODY$;
Источник
Please explain the inscrutable rules for when «commit» in a stored proc succeeds or causes a run-time error #1957
People have blogged about the fact that commit is legal in a PostgreSQL stored proc, starting with version 11—which is what yugabyteDB Version 1.3.0 uses. Here’s an example:
However, these blogs forget that psql (and therefore ysqlsh ) have AUTOCOMMIT set to True by default. They seem to do their tests in that mode and forget that the AUTOCOMMIT mode might be False . However, things change when the AUTOCOMMIT mode is False . See the following testcase.
I would like to hear the explanation for this weird behavior. It’s add odds with the definition of autocommitting: if the insert has autocommitted, then by definition there’s nothing to commit or rollback .
Following on from this, I see no reason why call my_proc() has to start a txn. It might do no more than a calculation. As I see it, it’s the first SQL statement that’s executed from the stored proc that should start a txn. Notice that within the present regime, and given that the weakest isolation level in yugabyte DB is repeatable read , you cannot implement a stored proc to watch a table in an infinite loop and to respond when it suffers an interesting change. Unless, that is, the non-intuitive workaround that I show below helps there.
Note: this remains to be tested.
Here, for completeness, is another manifestation of the same issue. This quietly succeeds:
But this fails with «ERROR: invalid transaction termination» :
The text was updated successfully, but these errors were encountered:
Источник
Недействительное завершение транзакции
У меня есть процедура, которая, когда я выполняю в dbeaver, отлично работает без проблем, однако, когда я вызываю ее из внешней программы, я получаю сообщение об ошибке ниже. Я не хочу копировать / вставлять здесь полную процедуру, потому что она довольно большая и работает в инструменте db. Я просто копирую / вставляю верх и низ. Что могло быть причиной этого?
Процедура:
Ошибка:
1 ответ
Управление транзакциями возможно только в вызовах CALL или DO с верхнего уровня или вложенных вызовах CALL или DO без какой-либо другой промежуточной команды. Например, если стек вызовов CALL proc1() → CALL proc2() → CALL proc3() , то вторая и третья процедуры могут выполнять действия по управлению транзакциями. Но если стек вызовов CALL proc1() → SELECT func2() → CALL proc3() , то последняя процедура не может управлять транзакцией из-за SELECT между ними.
Есть и другие, недокументированные ограничения:
Вы не можете явно начать транзакцию с помощью BEGIN и зафиксировать ее внутри транзакции. Таким образом, следующее не удастся:
Это может быть улучшено в будущих выпусках.
Все процедуры в стеке вызовов должны быть написаны на PL / pgSQL:
Как бы то ни было, контроль транзакций внутри процедур PostgreSQL несколько ограничен.
Если вы нарушите какое-либо из этих правил, вы получите сообщение об ошибке, описанное в вашем вопросе. Вероятно, вам придется обрабатывать транзакции в приложении, а не в процедуре — возможно, разделение процедуры на более мелкие части делает это возможным.
Источник
-
Home -
SQL, Database Testing -
«invalid transaction termination» in Postgres when trying to update a value using a function
This topic has been deleted. Only users with topic management privileges can see it.
-
I’m new to postgres. I defined a function like this:
CREATE OR REPLACE FUNCTION increment_count() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN UPDATE posts SET count=count+1 WHERE pid=NEW.pid; COMMIT; END; $$;
This gives me error
invalid transaction termination
-
You can not commit in a trigger, and you forgot the return statement.
CREATE OR REPLACE FUNCTION increment_count() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN UPDATE posts SET count=count+1 WHERE pid=NEW.pid; return null; END; $$;
In case of a BEFORE trigger, «return NEW» should be used!
Suggested Topics
-
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts1
Views -
2
0
Votes2
Posts1
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views -
2
0
Votes2
Posts0
Views