diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index a51822b5d55..1c916cf69ff 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -931,6 +931,64 @@ + + Pipelining + + + pipelining + protocol specification + + + + Use of the extended query protocol + allows pipelining, which means sending a series + of queries without waiting for earlier ones to complete. This reduces + the number of network round trips needed to complete a given series of + operations. However, the user must carefully consider the required + behavior if one of the steps fails, since later queries will already + be in flight to the server. + + + + One way to deal with that is to make the whole query series be a + single transaction, that is wrap it in BEGIN ... + COMMIT. However, this does not help if one wishes + for some of the commands to commit independently of others. + + + + The extended query protocol provides another way to manage this + concern, which is to omit sending Sync messages between steps that + are dependent. Since, after an error, the backend will skip command + messages until it finds Sync, this allows later commands in a pipeline + to be skipped automatically when an earlier one fails, without the + client having to manage that explicitly with BEGIN + and COMMIT. Independently-committable segments + of the pipeline can be separated by Sync messages. + + + + If the client has not issued an explicit BEGIN, + then each Sync ordinarily causes an implicit COMMIT + if the preceding step(s) succeeded, or an + implicit ROLLBACK if they failed. However, there + are a few DDL commands (such as CREATE DATABASE) + that cannot be executed inside a transaction block. If one of + these is executed in a pipeline, it will, upon success, force an + immediate commit to preserve database consistency. + A Sync immediately following one of these has no effect except to + respond with ReadyForQuery. + + + + When using this method, completion of the pipeline must be determined + by counting ReadyForQuery messages and waiting for that to reach the + number of Syncs sent. Counting command completion responses is + unreliable, since some of the commands may not be executed and thus not + produce a completion message. + + + Function Call diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 6c64eecf977..6db867dc884 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -3208,6 +3208,9 @@ AbortCurrentTransaction(void) * could issue more commands and possibly cause a failure after the statement * completes). Subtransactions are verboten too. * + * We must also set XACT_FLAGS_NEEDIMMEDIATECOMMIT in MyXactFlags, to ensure + * that postgres.c follows through by committing after the statement is done. + * * isTopLevel: passed down from ProcessUtility to determine whether we are * inside a function or multi-query querystring. (We will always fail if * this is false, but it's convenient to centralize the check here instead of @@ -3251,7 +3254,9 @@ PreventTransactionChain(bool isTopLevel, const char *stmtType) if (CurrentTransactionState->blockState != TBLOCK_DEFAULT && CurrentTransactionState->blockState != TBLOCK_STARTED) elog(FATAL, "cannot prevent transaction chain"); - /* all okay */ + + /* All okay. Set the flag to make sure the right thing happens later. */ + MyXactFlags |= XACT_FLAGS_NEEDIMMEDIATECOMMIT; } /* @@ -3350,6 +3355,13 @@ IsInTransactionChain(bool isTopLevel) CurrentTransactionState->blockState != TBLOCK_STARTED) return true; + /* + * If we tell the caller we're not in a transaction block, then inform + * postgres.c that it had better commit when the statement is done. + * Otherwise our report could be a lie. + */ + MyXactFlags |= XACT_FLAGS_NEEDIMMEDIATECOMMIT; + return false; } diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 40413fa9039..2f7e6c81e9b 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -1155,6 +1155,13 @@ exec_simple_query(const char *query_string) } else { + /* + * We had better not see XACT_FLAGS_NEEDIMMEDIATECOMMIT set if + * we're not calling finish_xact_command(). (The implicit + * transaction block should have prevented it from getting set.) + */ + Assert(!(MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT)); + /* * We need a CommandCounterIncrement after every query, except * those that start or end a transaction block. @@ -1903,32 +1910,16 @@ exec_execute_message(const char *portal_name, long max_rows) /* * We must copy the sourceText and prepStmtName into MessageContext in - * case the portal is destroyed during finish_xact_command. Can avoid the - * copy if it's not an xact command, though. + * case the portal is destroyed during finish_xact_command. We do not + * make a copy of the portalParams though, preferring to just not print + * them in that case. */ - if (is_xact_command) - { - sourceText = pstrdup(portal->sourceText); - if (portal->prepStmtName) - prepStmtName = pstrdup(portal->prepStmtName); - else - prepStmtName = ""; - - /* - * An xact command shouldn't have any parameters, which is a good - * thing because they wouldn't be around after finish_xact_command. - */ - portalParams = NULL; - } + sourceText = pstrdup(portal->sourceText); + if (portal->prepStmtName) + prepStmtName = pstrdup(portal->prepStmtName); else - { - sourceText = portal->sourceText; - if (portal->prepStmtName) - prepStmtName = portal->prepStmtName; - else - prepStmtName = ""; - portalParams = portal->portalParams; - } + prepStmtName = ""; + portalParams = portal->portalParams; /* * Report query to various monitoring facilities. @@ -2016,13 +2007,24 @@ exec_execute_message(const char *portal_name, long max_rows) if (completed) { - if (is_xact_command) + if (is_xact_command || (MyXactFlags & XACT_FLAGS_NEEDIMMEDIATECOMMIT)) { /* * If this was a transaction control statement, commit it. We * will start a new xact command for the next command (if any). + * Likewise if the statement required immediate commit. Without + * this provision, we wouldn't force commit until Sync is + * received, which creates a hazard if the client tries to + * pipeline immediate-commit statements. */ finish_xact_command(); + + /* + * These commands typically don't have any parameters, and even if + * one did we couldn't print them now because the storage went + * away during finish_xact_command. So pretend there were none. + */ + portalParams = NULL; } else { diff --git a/src/include/access/xact.h b/src/include/access/xact.h index 1bccc954c62..d7cd1587b22 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -98,6 +98,12 @@ extern int MyXactFlags; */ #define XACT_FLAGS_ACCESSEDTEMPNAMESPACE (1U << 2) +/* + * XACT_FLAGS_NEEDIMMEDIATECOMMIT - records whether the top level statement + * is one that requires immediate commit, such as CREATE DATABASE. + */ +#define XACT_FLAGS_NEEDIMMEDIATECOMMIT (1U << 3) + /* * start- and end-of-transaction callbacks for dynamically loaded modules */