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 */ /*