diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 46ec4acd40..edbab84935 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1078,7 +1078,9 @@ SELCT 1/0;<!-- this typo is intentional -->
    <para>
     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
+    are dependent.  When receiving another Execute message before finding
+    Sync, an implicit transaction block is started and it is closed when
+    receiving Sync.  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 <command>BEGIN</command>
@@ -1093,10 +1095,11 @@ SELCT 1/0;<!-- this typo is intentional -->
     implicit <command>ROLLBACK</command> if they failed.  However, there
     are a few DDL commands (such as <command>CREATE DATABASE</command>)
     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.
+    these is executed as a first command 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.
+    respond with ReadyForQuery.  Executing one of these in the middle
+    of a pipeline is not allowed and causes an error.
    </para>
 
    <para>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index ce1417b8f0..82242c082d 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -3596,13 +3596,6 @@ 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 078fbdb5a0..a2d3bcf469 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -2138,6 +2138,15 @@ exec_execute_message(const char *portal_name, long max_rows)
 	 */
 	start_xact_command();
 
+	/*
+	 * If we are processing more than one execute messages, start an implicit
+	 * transaction block for pipelining.
+	 */
+	if (MyXactFlags & XACT_FLAGS_FIRSTEXECUTE)
+		BeginImplicitTransactionBlock();
+	else
+		MyXactFlags |= XACT_FLAGS_FIRSTEXECUTE;
+
 	/*
 	 * If we re-issue an Execute protocol request against an existing portal,
 	 * then we are only fetching more rows rather than completely re-executing
@@ -4684,6 +4693,11 @@ PostgresMain(const char *dbname, const char *username)
 
 			case 'S':			/* sync */
 				pq_getmsgend(&input_message);
+				/*
+				 * If we're using an implicit transaction block for pipelining,
+				 * we must close that out first.
+				 */
+				EndImplicitTransactionBlock();
 				finish_xact_command();
 				send_ready_for_query = true;
 				break;
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 300baae120..169bc79535 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -113,6 +113,12 @@ extern PGDLLIMPORT int MyXactFlags;
  */
 #define XACT_FLAGS_NEEDIMMEDIATECOMMIT			(1U << 2)
 
+/*
+ * XACT_FLAGS_FIRSTEXECUTE - records whether the top level statement
+ * executes the first statement in a pipeline.
+ */
+#define XACT_FLAGS_FIRSTEXECUTE			(1U << 3)
+
 /*
  *	start- and end-of-transaction callbacks for dynamically loaded modules
  */
