]> granicus.if.org Git - postgresql/commitdiff
Make queries' locking of indexes more consistent.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 4 Apr 2019 19:12:51 +0000 (15:12 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 4 Apr 2019 19:12:58 +0000 (15:12 -0400)
The assertions added by commit b04aeb0a0 exposed that there are some
code paths wherein the executor will try to open an index without
holding any lock on it.  We do have some lock on the index's table,
so it seems likely that there's no fatal problem with this (for
instance, the index couldn't get dropped from under us).  Still,
it's bad practice and we should fix it.

To do so, remove the optimizations in ExecInitIndexScan and friends
that tried to avoid taking a lock on an index belonging to a target
relation, and just take the lock always.  In non-bug cases, this
will result in no additional shared-memory access, since we'll find
in the local lock table that we already have a lock of the desired
type; hence, no significant performance degradation should occur.

Also, adjust the planner and executor so that the type of lock taken
on an index is always identical to the type of lock taken for its table,
by relying on the recently added RangeTblEntry.rellockmode field.
This avoids some corner cases where that might not have been true
before (possibly resulting in extra locking overhead), and prevents
future maintenance issues from having multiple bits of logic that
all needed to be in sync.  In addition, this change removes all core
calls to ExecRelationIsTargetRelation, which avoids a possible O(N^2)
startup penalty for queries with large numbers of target relations.
(We'd probably remove that function altogether, were it not that we
advertise it as something that FDWs might want to use.)

Also adjust some places in selfuncs.c to not take any lock on indexes
they are transiently opening, since we can assume that plancat.c
did that already.

In passing, change gin_clean_pending_list() to take RowExclusiveLock
not AccessShareLock on its target index.  Although it's not clear that
that's actually a bug, it seemed very strange for a function that's
explicitly going to modify the index to use only AccessShareLock.

David Rowley, reviewed by Julien Rouhaud and Amit Langote,
a bit of further tweaking by me

Discussion: https://postgr.es/m/19465.1541636036@sss.pgh.pa.us

src/backend/access/gin/ginfast.c
src/backend/executor/execUtils.c
src/backend/executor/nodeBitmapIndexscan.c
src/backend/executor/nodeIndexonlyscan.c
src/backend/executor/nodeIndexscan.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/util/plancat.c
src/backend/utils/adt/selfuncs.c

index 70c7ef660319a3cbeb2ef9d5996912c92e42922d..2b3dd1c677f9cf3181baad7f27684c99c1164734 100644 (file)
@@ -1031,7 +1031,7 @@ Datum
 gin_clean_pending_list(PG_FUNCTION_ARGS)
 {
        Oid                     indexoid = PG_GETARG_OID(0);
-       Relation        indexRel = index_open(indexoid, AccessShareLock);
+       Relation        indexRel = index_open(indexoid, RowExclusiveLock);
        IndexBulkDeleteResult stats;
        GinState        ginstate;
 
@@ -1068,7 +1068,7 @@ gin_clean_pending_list(PG_FUNCTION_ARGS)
        initGinState(&ginstate, indexRel);
        ginInsertCleanup(&ginstate, true, true, true, &stats);
 
-       index_close(indexRel, AccessShareLock);
+       index_close(indexRel, RowExclusiveLock);
 
        PG_RETURN_INT64((int64) stats.pages_deleted);
 }
index 3b23de9fac5844bc2c67cc2e4d1d8f455fc8c1a1..2835a01e15a948f1ce573c7098709eee787535bc 100644 (file)
@@ -664,6 +664,10 @@ ExecCreateScanSlotFromOuterPlan(EState *estate,
  *
  *             Detect whether a relation (identified by rangetable index)
  *             is one of the target relations of the query.
+ *
+ * Note: This is currently no longer used in core.  We keep it around
+ * because FDWs may wish to use it to determine if their foreign table
+ * is a target relation.
  * ----------------------------------------------------------------
  */
 bool
index bd837d3cd8e13c5a146461e6553afc02697faf21..604f4f1132f5c5f3ebe0368e8ff66c9e0c2c6feb 100644 (file)
@@ -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)->rellockmode;
+       indexstate->biss_RelationDesc = index_open(node->indexid, lockmode);
 
        /*
         * Initialize index-specific scan state
index 2d954b722a7ded1487c1e0e51f3be0151472ecd7..7711728495c0bf71c1e2d64be3ea7041a3899ff2 100644 (file)
@@ -493,7 +493,7 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
 {
        IndexOnlyScanState *indexstate;
        Relation        currentRelation;
-       bool            relistarget;
+       LOCKMODE        lockmode;
        TupleDesc       tupDesc;
 
        /*
@@ -556,16 +556,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)->rellockmode;
+       indexstate->ioss_RelationDesc = index_open(node->indexid, lockmode);
 
        /*
         * Initialize index-specific scan state
index 8f39cc2b6be7984fe6b6fea19e2d3a6c4aa0399e..399ac0109c39e84462982513782e8b63b2b13668 100644 (file)
@@ -901,7 +901,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
 {
        IndexScanState *indexstate;
        Relation        currentRelation;
-       bool            relistarget;
+       LOCKMODE        lockmode;
 
        /*
         * create state structure
@@ -964,16 +964,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)->rellockmode;
+       indexstate->iss_RelationDesc = index_open(node->indexid, lockmode);
 
        /*
         * Initialize index-specific scan state
index c430189572e2e07d156ed5322e50c8057e04d6b5..e2cdc83613d64e8ef15b77cf2600b05b9c76c0ca 100644 (file)
@@ -6285,6 +6285,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
        /* Build RelOptInfo */
        rel = build_simple_rel(root, 1, NULL);
 
+       /* Rels are assumed already locked by the caller */
        heap = table_open(tableOid, NoLock);
        index = index_open(indexOid, NoLock);
 
index 31a378453615a4fd5f323adcc239f13a98c99303..33013313040da1fb58d1de57b8ac5584d8353fb4 100644 (file)
@@ -162,8 +162,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
        if (hasindex)
        {
                List       *indexoidlist;
-               ListCell   *l;
                LOCKMODE        lmode;
+               ListCell   *l;
 
                indexoidlist = RelationGetIndexList(relation);
 
@@ -172,13 +172,10 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
                 * need, and do not release it.  This saves a couple of trips to the
                 * shared lock manager while not creating any real loss of
                 * concurrency, because no schema changes could be happening on the
-                * index while we hold lock on the parent rel, and neither lock type
-                * blocks any other kind of index operation.
+                * index while we hold lock on the parent rel, and no lock type used
+                * for queries blocks any other kind of index operation.
                 */
-               if (rel->relid == root->parse->resultRelation)
-                       lmode = RowExclusiveLock;
-               else
-                       lmode = AccessShareLock;
+               lmode = root->simple_rte_array[varno]->rellockmode;
 
                foreach(l, indexoidlist)
                {
@@ -592,8 +589,8 @@ infer_arbiter_indexes(PlannerInfo *root)
        OnConflictExpr *onconflict = root->parse->onConflict;
 
        /* Iteration state */
+       RangeTblEntry *rte;
        Relation        relation;
-       Oid                     relationObjectId;
        Oid                     indexOidFromConstraint = InvalidOid;
        List       *indexList;
        ListCell   *l;
@@ -620,10 +617,9 @@ infer_arbiter_indexes(PlannerInfo *root)
         * the rewriter or when expand_inherited_rtentry() added it to the query's
         * rangetable.
         */
-       relationObjectId = rt_fetch(root->parse->resultRelation,
-                                                               root->parse->rtable)->relid;
+       rte = rt_fetch(root->parse->resultRelation, root->parse->rtable);
 
-       relation = table_open(relationObjectId, NoLock);
+       relation = table_open(rte->relid, NoLock);
 
        /*
         * Build normalized/BMS representation of plain indexed attributes, as
@@ -687,15 +683,14 @@ infer_arbiter_indexes(PlannerInfo *root)
                ListCell   *el;
 
                /*
-                * Extract info from the relation descriptor for the index.  We know
-                * that this is a target, so get lock type it is known will ultimately
-                * be required by the executor.
+                * Extract info from the relation descriptor for the index.  Obtain
+                * the same lock type that the executor will ultimately use.
                 *
                 * Let executor complain about !indimmediate case directly, because
                 * enforcement needs to occur there anyway when an inference clause is
                 * omitted.
                 */
-               idxRel = index_open(indexoid, RowExclusiveLock);
+               idxRel = index_open(indexoid, rte->rellockmode);
                idxForm = idxRel->rd_index;
 
                if (!idxForm->indisvalid)
index ecffffbd0c8a95af085f5e16e7c240c68b5dd87c..b41991315520ec12883ad4b253ee55270e9a58aa 100644 (file)
@@ -5187,11 +5187,10 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
 
                        /*
                         * Open the table and index so we can read from them.  We should
-                        * already have at least AccessShareLock on the table, but not
-                        * necessarily on the index.
+                        * already have some type of lock on each.
                         */
                        heapRel = table_open(rte->relid, NoLock);
-                       indexRel = index_open(index->indexoid, AccessShareLock);
+                       indexRel = index_open(index->indexoid, NoLock);
 
                        /* extract index key information from the index's pg_index info */
                        indexInfo = BuildIndexInfo(indexRel);
@@ -5305,7 +5304,7 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
                        /* Clean everything up */
                        ExecDropSingleTupleTableSlot(slot);
 
-                       index_close(indexRel, AccessShareLock);
+                       index_close(indexRel, NoLock);
                        table_close(heapRel, NoLock);
 
                        MemoryContextSwitchTo(oldcontext);
@@ -6472,9 +6471,10 @@ gincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
         */
        if (!index->hypothetical)
        {
-               indexRel = index_open(index->indexoid, AccessShareLock);
+               /* Lock should have already been obtained in plancat.c */
+               indexRel = index_open(index->indexoid, NoLock);
                ginGetStats(indexRel, &ginStats);
-               index_close(indexRel, AccessShareLock);
+               index_close(indexRel, NoLock);
        }
        else
        {
@@ -6781,11 +6781,12 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
                                                          &spc_seq_page_cost);
 
        /*
-        * Obtain some data from the index itself.
+        * Obtain some data from the index itself.  A lock should have already
+        * been obtained on the index in plancat.c.
         */
-       indexRel = index_open(index->indexoid, AccessShareLock);
+       indexRel = index_open(index->indexoid, NoLock);
        brinGetStats(indexRel, &statsData);
-       index_close(indexRel, AccessShareLock);
+       index_close(indexRel, NoLock);
 
        /*
         * Compute index correlation