diff --git a/src/backend/executor/nodeBitmapIndexscan.c b/src/backend/executor/nodeBitmapIndexscan.c
index bd837d3cd8..0d9b247bb4 100644
--- a/src/backend/executor/nodeBitmapIndexscan.c
+++ b/src/backend/executor/nodeBitmapIndexscan.c
@@ -211,7 +211,7 @@ BitmapIndexScanState *
 ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate, int eflags)
 {
 	BitmapIndexScanState *indexstate;
-	bool		relistarget;
+	LOCKMODE	lockmode;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -260,16 +260,9 @@ ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate, int eflags)
 	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
 		return indexstate;
 
-	/*
-	 * Open the index relation.
-	 *
-	 * If the parent table is one of the target relations of the query, then
-	 * InitPlan already opened and write-locked the index, so we can avoid
-	 * taking another lock here.  Otherwise we need a normal reader's lock.
-	 */
-	relistarget = ExecRelationIsTargetRelation(estate, node->scan.scanrelid);
-	indexstate->biss_RelationDesc = index_open(node->indexid,
-											   relistarget ? NoLock : AccessShareLock);
+	/* Open the index relation. */
+	lockmode = exec_rt_fetch(node->scan.scanrelid, estate)->idxlockmode;
+	indexstate->biss_RelationDesc = index_open(node->indexid, lockmode);
 
 	/*
 	 * Initialize index-specific scan state
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index b3f61dd1fc..3098c8342f 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -495,7 +495,7 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
 {
 	IndexOnlyScanState *indexstate;
 	Relation	currentRelation;
-	bool		relistarget;
+	LOCKMODE	lockmode;
 	TupleDesc	tupDesc;
 
 	/*
@@ -557,16 +557,9 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
 	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
 		return indexstate;
 
-	/*
-	 * Open the index relation.
-	 *
-	 * If the parent table is one of the target relations of the query, then
-	 * InitPlan already opened and write-locked the index, so we can avoid
-	 * taking another lock here.  Otherwise we need a normal reader's lock.
-	 */
-	relistarget = ExecRelationIsTargetRelation(estate, node->scan.scanrelid);
-	indexstate->ioss_RelationDesc = index_open(node->indexid,
-											   relistarget ? NoLock : AccessShareLock);
+	/* Open the index relation. */
+	lockmode = exec_rt_fetch(node->scan.scanrelid, estate)->idxlockmode;
+	indexstate->ioss_RelationDesc = index_open(node->indexid, lockmode);
 
 	/*
 	 * Initialize index-specific scan state
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 324356ec75..a9d3fa6f0b 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -919,7 +919,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
 {
 	IndexScanState *indexstate;
 	Relation	currentRelation;
-	bool		relistarget;
+	LOCKMODE	lockmode;
 
 	/*
 	 * create state structure
@@ -982,16 +982,9 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
 	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
 		return indexstate;
 
-	/*
-	 * Open the index relation.
-	 *
-	 * If the parent table is one of the target relations of the query, then
-	 * InitPlan already opened and write-locked the index, so we can avoid
-	 * taking another lock here.  Otherwise we need a normal reader's lock.
-	 */
-	relistarget = ExecRelationIsTargetRelation(estate, node->scan.scanrelid);
-	indexstate->iss_RelationDesc = index_open(node->indexid,
-											  relistarget ? NoLock : AccessShareLock);
+	/* Open the index relation. */
+	lockmode = exec_rt_fetch(node->scan.scanrelid, estate)->idxlockmode;
+	indexstate->iss_RelationDesc = index_open(node->indexid, lockmode);
 
 	/*
 	 * Initialize index-specific scan state
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b44ead269f..b90e834b2b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2354,6 +2354,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relid);
 	COPY_SCALAR_FIELD(relkind);
 	COPY_SCALAR_FIELD(rellockmode);
+	COPY_SCALAR_FIELD(idxlockmode);
 	COPY_NODE_FIELD(tablesample);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1e169e0b9c..275585d866 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2631,6 +2631,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relid);
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_SCALAR_FIELD(rellockmode);
+	COMPARE_SCALAR_FIELD(idxlockmode);
 	COMPARE_NODE_FIELD(tablesample);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f97cf37f1f..d8418814ec 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3020,6 +3020,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			WRITE_OID_FIELD(relid);
 			WRITE_CHAR_FIELD(relkind);
 			WRITE_INT_FIELD(rellockmode);
+			WRITE_INT_FIELD(idxlockmode);
 			WRITE_NODE_FIELD(tablesample);
 			break;
 		case RTE_SUBQUERY:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3b002778ad..e4b7df0cb7 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1363,6 +1363,7 @@ _readRangeTblEntry(void)
 			READ_OID_FIELD(relid);
 			READ_CHAR_FIELD(relkind);
 			READ_INT_FIELD(rellockmode);
+			READ_INT_FIELD(idxlockmode);
 			READ_NODE_FIELD(tablesample);
 			break;
 		case RTE_SUBQUERY:
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b2239728cf..41cd70ad7b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -127,6 +127,7 @@ typedef struct
 } WindowClauseSortData;
 
 /* Local functions */
+static void finalize_lockmodes(PlannedStmt *stmt);
 static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
 static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
 static void inheritance_planner(PlannerInfo *root);
@@ -570,9 +571,163 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 			result->jitFlags |= PGJIT_DEFORM;
 	}
 
+	/*
+	 * Determine correct lock modes for each rtable entry and the indexes
+	 * belonging to it.
+	 */
+	finalize_lockmodes(result);
+
 	return result;
 }
 
+typedef struct RelationLockmodeElem
+{
+	Oid			relid;		/* hash key -- must be first */
+	LOCKMODE	lockmode;
+} RelationLockmodeElem;
+
+
+/*
+ * finalize_lockmodes
+ *		Process stmt's rtable and determine the strongest lock level for each
+ *		distinct relation and upgrade weaker locks to the strongest lock level
+ *		for that relation.  Also determine the lock level required for each
+ *		relation's indexes and set that in the rel's idxlockmode field.
+ */
+static void
+finalize_lockmodes(PlannedStmt *stmt)
+{
+	Bitmapset  *resultRelids = NULL;
+	List	   *rtable = stmt->rtable;
+	ListCell   *lc;
+	int			relid;
+
+	/*
+	 * Determine the strongest lock level for each relation in rtable.  We
+	 * must apply the strongest lock of each relation if the same relation is
+	 * seen more than once and the lock levels vary.  This defends against
+	 * lock upgrade hazards we might see if we obtained the weaker lock
+	 * followed by the stronger lock level.  We need only attempt this when
+	 * there are multiple entries in the rtable.
+	 */
+	if (list_length(rtable) > 1)
+	{
+		RelationLockmodeElem   *elem;
+		HTAB	   *htab;
+		HASHCTL		ctl;
+		bool		found;
+		bool		applystrongest = false;
+
+		memset(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(RelationLockmodeElem);
+		ctl.hcxt = CurrentMemoryContext;
+
+		htab = hash_create("Lockmode table", list_length(rtable),
+			&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+
+		foreach(lc, rtable)
+		{
+			RangeTblEntry *rte = lfirst(lc);
+			Oid			reloid;
+
+			if (rte->relkind != RELKIND_RELATION)
+				continue;
+
+			reloid = rte->relid;
+			elem = (RelationLockmodeElem *)
+				hash_search(htab, &reloid, HASH_ENTER, &found);
+
+			/*
+			 * When we've seen this relation before and the lockmode varies
+			 * from the last time we saw it, then mark that we need to make
+			 * another pass over the list to apply the strongest of the seen
+			 * lock modes.
+			 */
+			if (found)
+			{
+				if (elem->lockmode != rte->rellockmode)
+				{
+					applystrongest = true;
+					elem->lockmode = Max(elem->lockmode, rte->rellockmode);
+				}
+			}
+			else
+				elem->lockmode = rte->rellockmode;
+		}
+
+		/*
+		 * If there are multiple instances of the same rel with varying lock
+		 * strengths then set the strongest lock level to each instance of
+		 * that relation.
+		 */
+		if (applystrongest)
+		{
+			foreach(lc, rtable)
+			{
+				RangeTblEntry *rte = lfirst(lc);
+				Oid			reloid;
+
+				if (rte->relkind != RELKIND_RELATION)
+					continue;
+
+				reloid = rte->relid;
+
+				elem = (RelationLockmodeElem *)
+					hash_search(htab, &reloid, HASH_FIND, &found);
+				Assert(found);
+
+				rte->rellockmode = elem->lockmode;
+			}
+		}
+
+		hash_destroy(htab);
+	}
+
+	if (stmt->commandType == CMD_INSERT || stmt->commandType == CMD_UPDATE)
+	{
+		foreach(lc, stmt->resultRelations)
+		{
+			relid = lfirst_int(lc);
+
+			resultRelids = bms_add_member(resultRelids, relid);
+		}
+	}
+
+	/* Determine index lock mode */
+	relid = 1;
+	foreach(lc, rtable)
+	{
+		RangeTblEntry *rte = lfirst(lc);
+
+		if (rte->relkind != RELKIND_RELATION)
+		{
+			relid++;
+			continue;
+		}
+
+		/*
+		 * SELECT always only will require an AccessShareLock on the indexes,
+		 * DELETE is the same since we're not modifying the index, only
+		 * marking the tuple on the heap as dead.
+		 */
+		if (stmt->commandType == CMD_SELECT || stmt->commandType == CMD_DELETE)
+			rte->idxlockmode = AccessShareLock;
+
+		/*
+		 * For INSERT and UPDATE we require a RowExclusiveLock only for result
+		 * relations. resultRelids will be NULL when not an INSERT or UPDATE.
+		 */
+		else if (bms_is_member(relid, resultRelids))
+			rte->idxlockmode = RowExclusiveLock;
+		else
+			rte->idxlockmode = AccessShareLock;
+
+		relid++;
+	}
+
+	bms_free(resultRelids);
+}
 
 /*--------------------
  * subquery_planner
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2fe14d7db2..fceb117819 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -990,6 +990,7 @@ typedef struct RangeTblEntry
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
+	int			idxlockmode;	/* lock level required for rel's indexes */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
 
 	/*
