v15-0002-parallel-SELECT-for-INSERT.patch
text/x-patch
Filename: v15-0002-parallel-SELECT-for-INSERT.patch
Type: text/x-patch
Part: 1
Message:
Re: Parallel INSERT SELECT take 2
From 781cac0481fe7c3b39fc59407c0a0a926a94b894 Mon Sep 17 00:00:00 2001
From: test <test>
Date: Fri, 8 May 2026 02:37:40 +0200
Subject: [PATCH v15 2/5] parallel-SELECT-for-INSERT
Enable parallel select for insert.
Prepare for entering parallel mode by assigning a TransactionId.
---
src/backend/access/transam/xact.c | 26 +++++++
src/backend/executor/execMain.c | 3 +
src/backend/optimizer/plan/planner.c | 9 +--
src/backend/optimizer/util/clauses.c | 100 ++++++++++++++++++++++++++-
src/include/access/xact.h | 15 ++++
src/include/optimizer/clauses.h | 2 +
6 files changed, 149 insertions(+), 6 deletions(-)
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 5586fbe5b07..fce8f08df4f 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -1123,6 +1123,32 @@ IsInParallelMode(void)
return s->parallelModeLevel != 0 || s->parallelChildXact;
}
+/*
+ * PrepareParallelModePlanExec
+ *
+ * Prepare for entering parallel mode plan execution, based on command-type.
+ */
+void
+PrepareParallelModePlanExec(CmdType commandType)
+{
+ if (IsModifySupportedInParallelMode(commandType))
+ {
+ Assert(!IsInParallelMode());
+
+ /*
+ * Prepare for entering parallel mode by assigning a TransactionId.
+ * Failure to do this now would result in heap_insert() subsequently
+ * attempting to assign a TransactionId whilst in parallel-mode, which
+ * is not allowed.
+ *
+ * This approach has a disadvantage in that if the underlying SELECT
+ * does not return any rows, then the TransactionId is not used,
+ * however that shouldn't happen in practice in many cases.
+ */
+ (void) GetCurrentTransactionId();
+ }
+}
+
/*
* CommandCounterIncrement
*/
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4b30f768680..b4803a29db4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1720,7 +1720,10 @@ ExecutePlan(QueryDesc *queryDesc,
estate->es_use_parallel_mode = use_parallel_mode;
if (use_parallel_mode)
+ {
+ PrepareParallelModePlanExec(estate->es_plannedstmt->commandType);
EnterParallelMode();
+ }
/*
* Loop until we've processed the proper number of tuples from the plan.
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f4689e7c9f8..2d136f44723 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -397,9 +397,9 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
/*
* Assess whether it's feasible to use parallel mode for this query. We
* can't do this in a standalone backend, or if the command will try to
- * modify any data, or if this is a cursor operation, or if GUCs are set
- * to values that don't permit parallelism, or if parallel-unsafe
- * functions are present in the query tree.
+ * modify any data (except for Insert), or if this is a cursor operation,
+ * or if GUCs are set to values that don't permit parallelism, or if
+ * parallel-unsafe functions are present in the query tree.
*
* (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
* MATERIALIZED VIEW to use parallel plans, but this is safe only because
@@ -417,7 +417,8 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
*/
if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
IsUnderPostmaster &&
- parse->commandType == CMD_SELECT &&
+ (parse->commandType == CMD_SELECT ||
+ is_parallel_allowed_for_modify(parse)) &&
!parse->hasModifyingCTE &&
max_parallel_workers_per_gather > 0 &&
!IsParallelWorker())
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index cd86311bb0b..b8a7a5e3f1d 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -21,6 +21,7 @@
#include "access/htup_details.h"
#include "access/table.h"
+#include "access/xact.h"
#include "catalog/pg_class.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_language.h"
@@ -171,7 +172,7 @@ static Query *substitute_actual_parameters_in_from(Query *expr,
static Node *substitute_actual_parameters_in_from_mutator(Node *node,
substitute_actual_parameters_in_from_context *context);
static bool pull_paramids_walker(Node *node, Bitmapset **context);
-
+static bool max_parallel_hazard_test(char proparallel, max_parallel_hazard_context *context);
/*****************************************************************************
* Aggregate-function clause manipulation
@@ -746,12 +747,45 @@ contain_volatile_functions_not_nextval_walker(Node *node, void *context)
char
max_parallel_hazard(Query *parse)
{
+ bool found;
max_parallel_hazard_context context;
context.max_hazard = PROPARALLEL_SAFE;
context.max_interesting = PROPARALLEL_UNSAFE;
context.safe_param_ids = NIL;
- (void) max_parallel_hazard_walker((Node *) parse, &context);
+
+ /* try to determine the worst hazard for the parsed query */
+ found = max_parallel_hazard_walker((Node *) parse, &context);
+
+ /*
+ * If walking the parse tree did not determine the hazard, check the value
+ * for the target relation.
+ *
+ * FIXME Under what conditions can the walker fail to determine the hazard?
+ * When there are no expressions, or something else?
+ *
+ * FIXME Why should we check the per-relation hazard only when the walker
+ * fails to determine a value? Shouldn't we be checking both places?
+ */
+ if (!found &&
+ IsModifySupportedInParallelMode(parse->commandType))
+ {
+ RangeTblEntry *rte;
+ Relation target_rel;
+
+ rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ /*
+ * The target table is already locked by the caller (this is done in the
+ * parse/analyze phase), and remains locked until end-of-transaction.
+ */
+ target_rel = table_open(rte->relid, NoLock);
+
+ (void) max_parallel_hazard_test(target_rel->rd_rel->relparalleldml,
+ &context);
+ table_close(target_rel, NoLock);
+ }
+
return context.max_hazard;
}
@@ -985,6 +1019,68 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
context);
}
+/*
+ * is_parallel_allowed_for_modify
+ *
+ * Check at a high-level if parallel mode is able to be used for the specified
+ * table-modification statement. Currently, only some INSERT cases are allowed.
+ *
+ * It's not possible in the following cases:
+ *
+ * 1) INSERT...ON CONFLICT...DO UPDATE
+ * 2) INSERT without SELECT
+ *
+ * Note: We don't do in-depth parallel-safety checks here, we do only the
+ * cheaper tests that can quickly exclude obvious cases for which
+ * parallelism isn't supported, to avoid having to do further parallel-safety
+ * checks for these.
+ *
+ * XXX Isn't this somewhat backwards? Shouldn't we assume "false" and only
+ * allow parallel DML for "obviously safe" cases? So that we don't allow it
+ * in unsafe cases?
+ */
+bool
+is_parallel_allowed_for_modify(Query *parse)
+{
+ bool hasSubQuery;
+ RangeTblEntry *rte;
+ ListCell *lc;
+
+ if (!IsModifySupportedInParallelMode(parse->commandType))
+ return false;
+
+ /*
+ * UPDATE is not currently supported in parallel-mode, so prohibit
+ * INSERT...ON CONFLICT...DO UPDATE...
+ *
+ * In order to support update, even if only in the leader, some further
+ * work would need to be done. A mechanism would be needed for sharing
+ * combo-cids between leader and workers during parallel-mode, since for
+ * example, the leader might generate a combo-cid and it needs to be
+ * propagated to the workers.
+ */
+ if (parse->commandType == CMD_INSERT &&
+ parse->onConflict != NULL &&
+ parse->onConflict->action == ONCONFLICT_UPDATE)
+ return false;
+
+ /*
+ * If there is no underlying SELECT, a parallel insert operation is not
+ * desirable.
+ */
+ hasSubQuery = false;
+ foreach(lc, parse->rtable)
+ {
+ rte = lfirst_node(RangeTblEntry, lc);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ hasSubQuery = true;
+ break;
+ }
+ }
+
+ return hasSubQuery;
+}
/*****************************************************************************
* Check clauses for nonstrict functions
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index a8cbdf247c8..01335e42e64 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -534,5 +534,20 @@ extern void ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parse
extern void EnterParallelMode(void);
extern void ExitParallelMode(void);
extern bool IsInParallelMode(void);
+extern void PrepareParallelModePlanExec(CmdType commandType);
+
+/*
+ * IsModifySupportedInParallelMode
+ *
+ * Indicates whether execution of the specified table-modification command
+ * (INSERT/UPDATE/DELETE) in parallel-mode is supported, subject to certain
+ * parallel-safety conditions.
+ */
+static inline bool
+IsModifySupportedInParallelMode(CmdType commandType)
+{
+ /* Currently only INSERT is supported */
+ return (commandType == CMD_INSERT);
+}
#endif /* XACT_H */
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 853a28c0007..e96fd4bac7e 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -56,4 +56,6 @@ extern Query *inline_function_in_from(PlannerInfo *root,
extern Bitmapset *pull_paramids(Expr *expr);
+extern bool is_parallel_allowed_for_modify(Query *parse);
+
#endif /* CLAUSES_H */
--
2.53.0