Advance transaction timestamp for intra-procedure transactions.

Per discussion, this behavior seems less astonishing than not doing so.

Peter Eisentraut and Tom Lane

Discussion: https://postgr.es/m/20180920234040.GC29981@momjian.us
This commit is contained in:
Tom Lane 2018-10-08 16:16:36 -04:00
parent 8569ef63f4
commit 1145c26b74
5 changed files with 91 additions and 8 deletions

View File

@ -1906,20 +1906,26 @@ StartTransaction(void)
TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId); TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
/* /*
* set transaction_timestamp() (a/k/a now()). We want this to be the same * set transaction_timestamp() (a/k/a now()). Normally, we want this to
* as the first command's statement_timestamp(), so don't do a fresh * be the same as the first command's statement_timestamp(), so don't do a
* GetCurrentTimestamp() call (which'd be expensive anyway). In a * fresh GetCurrentTimestamp() call (which'd be expensive anyway). But
* parallel worker, this should already have been provided by a call to * for transactions started inside procedures (i.e., nonatomic SPI
* contexts), we do need to advance the timestamp. Also, in a parallel
* worker, the timestamp should already have been provided by a call to
* SetParallelStartTimestamps(). * SetParallelStartTimestamps().
*
* Also, mark xactStopTimestamp as unset.
*/ */
if (!IsParallelWorker()) if (!IsParallelWorker())
xactStartTimestamp = stmtStartTimestamp; {
if (!SPI_inside_nonatomic_context())
xactStartTimestamp = stmtStartTimestamp;
else
xactStartTimestamp = GetCurrentTimestamp();
}
else else
Assert(xactStartTimestamp != 0); Assert(xactStartTimestamp != 0);
xactStopTimestamp = 0;
pgstat_report_xact_timestamp(xactStartTimestamp); pgstat_report_xact_timestamp(xactStartTimestamp);
/* Mark xactStopTimestamp as unset. */
xactStopTimestamp = 0;
/* /*
* initialize current transaction state fields * initialize current transaction state fields

View File

@ -423,6 +423,19 @@ AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid)
} }
} }
/*
* Are we executing inside a procedure (that is, a nonatomic SPI context)?
*/
bool
SPI_inside_nonatomic_context(void)
{
if (_SPI_current == NULL)
return false; /* not in any SPI context at all */
if (_SPI_current->atomic)
return false; /* it's atomic (ie function not procedure) */
return true;
}
/* Parse, plan, and execute a query string */ /* Parse, plan, and execute a query string */
int int

View File

@ -166,5 +166,6 @@ extern void SPI_rollback(void);
extern void SPICleanup(void); extern void SPICleanup(void);
extern void AtEOXact_SPI(bool isCommit); extern void AtEOXact_SPI(bool isCommit);
extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid); extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid);
extern bool SPI_inside_nonatomic_context(void);
#endif /* SPI_H */ #endif /* SPI_H */

View File

@ -493,6 +493,36 @@ CALL transaction_test10b(10);
9 9
(1 row) (1 row)
-- transaction timestamp vs. statement timestamp
CREATE PROCEDURE transaction_test11()
LANGUAGE plpgsql
AS $$
DECLARE
s1 timestamp with time zone;
s2 timestamp with time zone;
s3 timestamp with time zone;
t1 timestamp with time zone;
t2 timestamp with time zone;
t3 timestamp with time zone;
BEGIN
s1 := statement_timestamp();
t1 := transaction_timestamp();
ASSERT s1 = t1;
PERFORM pg_sleep(0.001);
COMMIT;
s2 := statement_timestamp();
t2 := transaction_timestamp();
ASSERT s2 = s1;
ASSERT t2 > t1;
PERFORM pg_sleep(0.001);
ROLLBACK;
s3 := statement_timestamp();
t3 := transaction_timestamp();
ASSERT s3 = s1;
ASSERT t3 > t2;
END;
$$;
CALL transaction_test11();
DROP TABLE test1; DROP TABLE test1;
DROP TABLE test2; DROP TABLE test2;
DROP TABLE test3; DROP TABLE test3;

View File

@ -412,6 +412,39 @@ $$;
CALL transaction_test10b(10); CALL transaction_test10b(10);
-- transaction timestamp vs. statement timestamp
CREATE PROCEDURE transaction_test11()
LANGUAGE plpgsql
AS $$
DECLARE
s1 timestamp with time zone;
s2 timestamp with time zone;
s3 timestamp with time zone;
t1 timestamp with time zone;
t2 timestamp with time zone;
t3 timestamp with time zone;
BEGIN
s1 := statement_timestamp();
t1 := transaction_timestamp();
ASSERT s1 = t1;
PERFORM pg_sleep(0.001);
COMMIT;
s2 := statement_timestamp();
t2 := transaction_timestamp();
ASSERT s2 = s1;
ASSERT t2 > t1;
PERFORM pg_sleep(0.001);
ROLLBACK;
s3 := statement_timestamp();
t3 := transaction_timestamp();
ASSERT s3 = s1;
ASSERT t3 > t2;
END;
$$;
CALL transaction_test11();
DROP TABLE test1; DROP TABLE test1;
DROP TABLE test2; DROP TABLE test2;
DROP TABLE test3; DROP TABLE test3;