diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index fcff12ae26ef119d5cb80658041259f8c74a9f57..6102c70f47c185a6af4f43800d8062a2a4e05646 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.214 2009/04/02 22:44:10 momjian Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.215 2009/04/23 00:23:45 tgl Exp $ --> <chapter Id="runtime-config"> <title>Server Configuration</title> @@ -785,18 +785,18 @@ SET ENABLE_SEQSCAN TO OFF; <quote>prepared</> state simultaneously (see <xref linkend="sql-prepare-transaction" endterm="sql-prepare-transaction-title">). - Setting this parameter to zero disables the prepared-transaction - feature. - The default is five transactions. + Setting this parameter to zero (which is the default) + disables the prepared-transaction feature. This parameter can only be set at server start. </para> <para> - If you are not using prepared transactions, this parameter may as - well be set to zero. If you are using them, you will probably - want <varname>max_prepared_transactions</varname> to be at least - as large as <xref linkend="guc-max-connections">, to avoid unwanted - failures at the prepare step. + If you are not planning to use prepared transactions, this parameter + should be set to zero to prevent accidental creation of prepared + transactions. If you are using prepared transactions, you will + probably want <varname>max_prepared_transactions</varname> to be at + least as large as <xref linkend="guc-max-connections">, so that every + session can have a prepared transaction pending. </para> <para> diff --git a/doc/src/sgml/ref/prepare_transaction.sgml b/doc/src/sgml/ref/prepare_transaction.sgml index 82973c4751eacb66da177c3fc6c0ae483592e05b..b8b8d8aca83149f23c3451bd938f5e264c75c396 100644 --- a/doc/src/sgml/ref/prepare_transaction.sgml +++ b/doc/src/sgml/ref/prepare_transaction.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/prepare_transaction.sgml,v 1.8 2008/11/14 10:22:47 petere Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/prepare_transaction.sgml,v 1.9 2009/04/23 00:23:45 tgl Exp $ PostgreSQL documentation --> @@ -30,7 +30,7 @@ PREPARE TRANSACTION <replaceable class="PARAMETER">transaction_id</replaceable> <para> <command>PREPARE TRANSACTION</command> prepares the current transaction - for two-phase commit. After this command, the transaction is no longer + for two-phase commit. After this command, the transaction is no longer associated with the current session; instead, its state is fully stored on disk, and there is a very high probability that it can be committed successfully, even if a database crash occurs before the commit is @@ -100,7 +100,7 @@ PREPARE TRANSACTION <replaceable class="PARAMETER">transaction_id</replaceable> If the transaction modified any run-time parameters with <command>SET</> (without the <literal>LOCAL</> option), those effects persist after <command>PREPARE TRANSACTION</>, and will not - be affected by any later <command>COMMIT PREPARED</command> or + be affected by any later <command>COMMIT PREPARED</command> or <command>ROLLBACK PREPARED</command>. Thus, in this one respect <command>PREPARE TRANSACTION</> acts more like <command>COMMIT</> than <command>ROLLBACK</>. @@ -112,26 +112,28 @@ PREPARE TRANSACTION <replaceable class="PARAMETER">transaction_id</replaceable> system view. </para> - <para> - From a performance standpoint, it is unwise to leave transactions in - the prepared state for a long time: this will for instance interfere with - the ability of <command>VACUUM</> to reclaim storage. Keep in mind also - that the transaction continues to hold whatever locks it held. - The intended - usage of the feature is that a prepared transaction will normally be - committed or rolled back as soon as an external transaction manager - has verified that other databases are also prepared to commit. - </para> - - <para> - If you make any serious use of prepared transactions, you will probably - want to increase the value of <xref - linkend="guc-max-prepared-transactions">, as the default setting is - quite small (to avoid wasting resources for those who don't use it). - It is recommendable to make it at least equal to - <xref linkend="guc-max-connections">, so that every session can have - a prepared transaction pending. - </para> + <caution> + <para> + It is unwise to leave transactions in the prepared state for a long time. + This will interfere with the ability of <command>VACUUM</> to reclaim + storage, and in extreme cases could cause the database to shut down + to prevent transaction ID wraparound (see <xref + linkend="vacuum-for-wraparound">). Keep in mind also that the transaction + continues to hold whatever locks it held. The intended usage of the + feature is that a prepared transaction will normally be committed or + rolled back as soon as an external transaction manager has verified that + other databases are also prepared to commit. + </para> + + <para> + If you have not set up an external transaction manager to track prepared + transactions and ensure they get closed out promptly, it is best to keep + the prepared-transaction feature disabled by setting + <xref linkend="guc-max-prepared-transactions"> to zero. This will + prevent accidental creation of prepared transactions that might then + be forgotten and eventually cause problems. + </para> + </caution> </refsect1> <refsect1 id="sql-prepare-transaction-examples"> @@ -139,7 +141,7 @@ PREPARE TRANSACTION <replaceable class="PARAMETER">transaction_id</replaceable> <para> Prepare the current transaction for two-phase commit, using <literal>foobar</> as the transaction identifier: - + <programlisting> PREPARE TRANSACTION 'foobar'; </programlisting> diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index b738a4cef46cf3a63f4b1c3e2e676a2a36610089..4685ccdf10b60bcd7e6f6570beb49952ad911add 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/twophase.c,v 1.51 2009/01/01 17:23:36 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/twophase.c,v 1.52 2009/04/23 00:23:45 tgl Exp $ * * NOTES * Each global transaction is associated with a global transaction @@ -68,7 +68,7 @@ #define TWOPHASE_DIR "pg_twophase" /* GUC variable, can't be changed after startup */ -int max_prepared_xacts = 5; +int max_prepared_xacts = 0; /* * This struct describes one global transaction that is in prepared state @@ -228,6 +228,13 @@ MarkAsPreparing(TransactionId xid, const char *gid, errmsg("transaction identifier \"%s\" is too long", gid))); + /* fail immediately if feature is disabled */ + if (max_prepared_xacts == 0) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("prepared transactions are disabled"), + errhint("Set max_prepared_transactions to a nonzero value."))); + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); /* diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 5c0e6279877327146456e1536cd0ea905ebfa267..029b2f2deb7b4ad62a9484f4b8e82d6f664ba73e 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -6,7 +6,7 @@ * Copyright (c) 2000-2009, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.83 2009/01/01 17:23:36 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.84 2009/04/23 00:23:45 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -86,14 +86,16 @@ GetNewTransactionId(bool isSubXact) (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("database is not accepting commands to avoid wraparound data loss in database \"%s\"", NameStr(ShmemVariableCache->limit_datname)), - errhint("Stop the postmaster and use a standalone backend to vacuum database \"%s\".", + errhint("Stop the postmaster and use a standalone backend to vacuum database \"%s\".\n" + "You might also need to commit or roll back old prepared transactions.", NameStr(ShmemVariableCache->limit_datname)))); else if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidWarnLimit)) ereport(WARNING, (errmsg("database \"%s\" must be vacuumed within %u transactions", NameStr(ShmemVariableCache->limit_datname), ShmemVariableCache->xidWrapLimit - xid), - errhint("To avoid a database shutdown, execute a database-wide VACUUM in \"%s\".", + errhint("To avoid a database shutdown, execute a database-wide VACUUM in \"%s\".\n" + "You might also need to commit or roll back old prepared transactions.", NameStr(ShmemVariableCache->limit_datname)))); } @@ -299,7 +301,8 @@ SetTransactionIdLimit(TransactionId oldest_datfrozenxid, (errmsg("database \"%s\" must be vacuumed within %u transactions", NameStr(*oldest_datname), xidWrapLimit - curXid), - errhint("To avoid a database shutdown, execute a database-wide VACUUM in \"%s\".", + errhint("To avoid a database shutdown, execute a database-wide VACUUM in \"%s\".\n" + "You might also need to commit or roll back old prepared transactions.", NameStr(*oldest_datname)))); } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 7313dc0e5851a8cd88ac5c66bfc88f49f7c1db8e..23fb43647d10c190ad7ebf22efd0aa6579bc84f7 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -10,7 +10,7 @@ * Written by Peter Eisentraut <peter_e@gmx.net>. * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.502 2009/04/07 23:27:34 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.503 2009/04/23 00:23:45 tgl Exp $ * *-------------------------------------------------------------------- */ @@ -1504,7 +1504,7 @@ static struct config_int ConfigureNamesInt[] = NULL }, &max_prepared_xacts, - 5, 0, INT_MAX, NULL, NULL + 0, 0, INT_MAX / 4, NULL, NULL }, #ifdef LOCK_DEBUG diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 1e9379ac2f110f674c2cce12fe659b0eb4ad1bb6..3f7b43f0ccc54d0a966a886a575d4cc32cb61217 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -106,10 +106,12 @@ #shared_buffers = 32MB # min 128kB # (change requires restart) #temp_buffers = 8MB # min 800kB -#max_prepared_transactions = 5 # can be 0 or more +#max_prepared_transactions = 0 # zero disables the feature # (change requires restart) # Note: Increasing max_prepared_transactions costs ~600 bytes of shared memory # per transaction slot, plus lock space (see max_locks_per_transaction). +# It is not advisable to set max_prepared_transactions nonzero unless you +# actively intend to use prepared transactions. #work_mem = 1MB # min 64kB #maintenance_work_mem = 16MB # min 1MB #max_stack_depth = 2MB # min 100kB diff --git a/src/test/regress/expected/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out index 535dbe4cbf5fd792b4280ae754de5b949742cb93..292962ab7b3d7f04eb363cea5138079f2ed6d6a4 100644 --- a/src/test/regress/expected/prepared_xacts.out +++ b/src/test/regress/expected/prepared_xacts.out @@ -214,4 +214,6 @@ SELECT gid FROM pg_prepared_xacts; -- Clean up DROP TABLE pxtest2; +DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled +ERROR: table "pxtest3" does not exist DROP TABLE pxtest4; diff --git a/src/test/regress/expected/prepared_xacts_1.out b/src/test/regress/expected/prepared_xacts_1.out new file mode 100644 index 0000000000000000000000000000000000000000..991a11bdfa96468f534fce4bce00981ff60ae5da --- /dev/null +++ b/src/test/regress/expected/prepared_xacts_1.out @@ -0,0 +1,223 @@ +-- +-- PREPARED TRANSACTIONS (two-phase commit) +-- +-- We can't readily test persistence of prepared xacts within the +-- regression script framework, unfortunately. Note that a crash +-- isn't really needed ... stopping and starting the postmaster would +-- be enough, but we can't even do that here. +-- create a simple table that we'll use in the tests +CREATE TABLE pxtest1 (foobar VARCHAR(10)); +INSERT INTO pxtest1 VALUES ('aaa'); +-- Test PREPARE TRANSACTION +BEGIN; +UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa'; +SELECT * FROM pxtest1; + foobar +-------- + bbb +(1 row) + +PREPARE TRANSACTION 'foo1'; +ERROR: prepared transactions are disabled +HINT: Set max_prepared_transactions to a nonzero value. +SELECT * FROM pxtest1; + foobar +-------- + aaa +(1 row) + +-- Test pg_prepared_xacts system view +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +-- Test ROLLBACK PREPARED +ROLLBACK PREPARED 'foo1'; +ERROR: prepared transaction with identifier "foo1" does not exist +SELECT * FROM pxtest1; + foobar +-------- + aaa +(1 row) + +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +-- Test COMMIT PREPARED +BEGIN; +INSERT INTO pxtest1 VALUES ('ddd'); +SELECT * FROM pxtest1; + foobar +-------- + aaa + ddd +(2 rows) + +PREPARE TRANSACTION 'foo2'; +ERROR: prepared transactions are disabled +HINT: Set max_prepared_transactions to a nonzero value. +SELECT * FROM pxtest1; + foobar +-------- + aaa +(1 row) + +COMMIT PREPARED 'foo2'; +ERROR: prepared transaction with identifier "foo2" does not exist +SELECT * FROM pxtest1; + foobar +-------- + aaa +(1 row) + +-- Test duplicate gids +BEGIN; +UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd'; +SELECT * FROM pxtest1; + foobar +-------- + aaa +(1 row) + +PREPARE TRANSACTION 'foo3'; +ERROR: prepared transactions are disabled +HINT: Set max_prepared_transactions to a nonzero value. +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +BEGIN; +INSERT INTO pxtest1 VALUES ('fff'); +SELECT * FROM pxtest1; + foobar +-------- + aaa + fff +(2 rows) + +-- This should fail, because the gid foo3 is already in use +PREPARE TRANSACTION 'foo3'; +ERROR: prepared transactions are disabled +HINT: Set max_prepared_transactions to a nonzero value. +SELECT * FROM pxtest1; + foobar +-------- + aaa +(1 row) + +ROLLBACK PREPARED 'foo3'; +ERROR: prepared transaction with identifier "foo3" does not exist +SELECT * FROM pxtest1; + foobar +-------- + aaa +(1 row) + +-- Clean up +DROP TABLE pxtest1; +-- Test subtransactions +BEGIN; + CREATE TABLE pxtest2 (a int); + INSERT INTO pxtest2 VALUES (1); + SAVEPOINT a; + INSERT INTO pxtest2 VALUES (2); + ROLLBACK TO a; + SAVEPOINT b; + INSERT INTO pxtest2 VALUES (3); +PREPARE TRANSACTION 'regress-one'; +ERROR: prepared transactions are disabled +HINT: Set max_prepared_transactions to a nonzero value. +CREATE TABLE pxtest3(fff int); +-- Test shared invalidation +BEGIN; + DROP TABLE pxtest3; + CREATE TABLE pxtest4 (a int); + INSERT INTO pxtest4 VALUES (1); + INSERT INTO pxtest4 VALUES (2); + DECLARE foo CURSOR FOR SELECT * FROM pxtest4; + -- Fetch 1 tuple, keeping the cursor open + FETCH 1 FROM foo; + a +--- + 1 +(1 row) + +PREPARE TRANSACTION 'regress-two'; +ERROR: prepared transactions are disabled +HINT: Set max_prepared_transactions to a nonzero value. +-- No such cursor +FETCH 1 FROM foo; +ERROR: cursor "foo" does not exist +-- Table doesn't exist, the creation hasn't been committed yet +SELECT * FROM pxtest2; +ERROR: relation "pxtest2" does not exist +LINE 1: SELECT * FROM pxtest2; + ^ +-- There should be two prepared transactions +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +-- pxtest3 should be locked because of the pending DROP +set statement_timeout to 2000; +SELECT * FROM pxtest3; + fff +----- +(0 rows) + +reset statement_timeout; +-- Disconnect, we will continue testing in a different backend +\c - +-- There should still be two prepared transactions +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +-- pxtest3 should still be locked because of the pending DROP +set statement_timeout to 2000; +SELECT * FROM pxtest3; + fff +----- +(0 rows) + +reset statement_timeout; +-- Commit table creation +COMMIT PREPARED 'regress-one'; +ERROR: prepared transaction with identifier "regress-one" does not exist +\d pxtest2 +SELECT * FROM pxtest2; +ERROR: relation "pxtest2" does not exist +LINE 1: SELECT * FROM pxtest2; + ^ +-- There should be one prepared transaction +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +-- Commit table drop +COMMIT PREPARED 'regress-two'; +ERROR: prepared transaction with identifier "regress-two" does not exist +SELECT * FROM pxtest3; + fff +----- +(0 rows) + +-- There should be no prepared transactions +SELECT gid FROM pg_prepared_xacts; + gid +----- +(0 rows) + +-- Clean up +DROP TABLE pxtest2; +ERROR: table "pxtest2" does not exist +DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled +DROP TABLE pxtest4; +ERROR: table "pxtest4" does not exist diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 7bd297f20c13e2b738cb89bb9e72dbb4719a1431..b51d6ec2b5bc8640fc548ebc33f340dbb99b539f 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -11,7 +11,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/test/regress/pg_regress.c,v 1.61 2009/02/12 13:26:03 petere Exp $ + * $PostgreSQL: pgsql/src/test/regress/pg_regress.c,v 1.62 2009/04/23 00:23:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2037,6 +2037,8 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc if (temp_install) { + FILE *pg_conf; + /* * Prepare the temp installation */ @@ -2092,20 +2094,29 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc exit_nicely(2); } - /* add any extra config specified to the postgresql.conf */ + /* + * Adjust the default postgresql.conf as needed for regression testing. + * The user can specify a file to be appended; in any case we set + * max_prepared_transactions to enable testing of prepared xacts. + * (Note: to reduce the probability of unexpected shmmax failures, + * don't set max_prepared_transactions any higher than actually + * needed by the prepared_xacts regression test.) + */ + snprintf(buf, sizeof(buf), "%s/data/postgresql.conf", temp_install); + pg_conf = fopen(buf, "a"); + if (pg_conf == NULL) + { + fprintf(stderr, _("\n%s: could not open \"%s\" for adding extra config: %s\n"), progname, buf, strerror(errno)); + exit_nicely(2); + } + fputs("\n# Configuration added by pg_regress\n\n", pg_conf); + fputs("max_prepared_transactions = 2\n", pg_conf); + if (temp_config != NULL) { FILE *extra_conf; - FILE *pg_conf; char line_buf[1024]; - snprintf(buf, sizeof(buf), "%s/data/postgresql.conf", temp_install); - pg_conf = fopen(buf, "a"); - if (pg_conf == NULL) - { - fprintf(stderr, _("\n%s: could not open \"%s\" for adding extra config: %s\n"), progname, buf, strerror(errno)); - exit_nicely(2); - } extra_conf = fopen(temp_config, "r"); if (extra_conf == NULL) { @@ -2115,9 +2126,10 @@ regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc while (fgets(line_buf, sizeof(line_buf), extra_conf) != NULL) fputs(line_buf, pg_conf); fclose(extra_conf); - fclose(pg_conf); } + fclose(pg_conf); + /* * Check if there is a postmaster running already. */ diff --git a/src/test/regress/sql/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql index 337567271de1da450372dc3776b1390967ed971f..39d323a15b69a94450bf96fd3ccfe4b419446c63 100644 --- a/src/test/regress/sql/prepared_xacts.sql +++ b/src/test/regress/sql/prepared_xacts.sql @@ -134,4 +134,5 @@ SELECT gid FROM pg_prepared_xacts; -- Clean up DROP TABLE pxtest2; +DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled DROP TABLE pxtest4;