diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 32deff873c..63b3224617 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1050,6 +1050,64 @@ SELCT 1/0; + + 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 3055a70718..ac5ce071d1 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -3370,6 +3370,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. (We will always fail if this is false, but it's * convenient to centralize the check here instead of making callers do it.) @@ -3411,7 +3414,9 @@ PreventInTransactionBlock(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; } /* @@ -3508,6 +3513,13 @@ IsInTransactionBlock(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 3cd4c6b428..5bee74363c 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -1272,6 +1272,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. @@ -2082,32 +2089,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. @@ -2206,13 +2197,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 3c66119d94..f00fe4eb58 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -103,6 +103,12 @@ extern int MyXactFlags; */ #define XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK (1U << 1) +/* + * XACT_FLAGS_NEEDIMMEDIATECOMMIT - records whether the top level statement + * is one that requires immediate commit, such as CREATE DATABASE. + */ +#define XACT_FLAGS_NEEDIMMEDIATECOMMIT (1U << 2) + /* * start- and end-of-transaction callbacks for dynamically loaded modules */