summaryrefslogtreecommitdiff
path: root/src/backend/catalog/index.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/catalog/index.c')
-rw-r--r--src/backend/catalog/index.c141
1 files changed, 117 insertions, 24 deletions
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 55f69fa56da..40712988cd3 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1126,7 +1126,7 @@ BuildIndexInfo(Relation index)
/* other info */
ii->ii_Unique = indexStruct->indisunique;
- ii->ii_ReadyForInserts = indexStruct->indisready;
+ ii->ii_ReadyForInserts = IndexIsReady(indexStruct);
/* initialize index-build state to default */
ii->ii_Concurrent = false;
@@ -1465,8 +1465,20 @@ index_build(Relation heapRelation,
* index's usability horizon. Moreover, we *must not* try to change
* the index's pg_index entry while reindexing pg_index itself, and this
* optimization nicely prevents that.
- */
- if (indexInfo->ii_BrokenHotChain && !isreindex)
+ *
+ * We also need not set indcheckxmin during a concurrent index build,
+ * because we won't set indisvalid true until all transactions that care
+ * about the broken HOT chains are gone.
+ *
+ * Therefore, this code path can only be taken during non-concurrent
+ * CREATE INDEX. Thus the fact that heap_update will set the pg_index
+ * tuple's xmin doesn't matter, because that tuple was created in the
+ * current transaction anyway. That also means we don't need to worry
+ * about any concurrent readers of the tuple; no other transaction can see
+ * it yet.
+ */
+ if (indexInfo->ii_BrokenHotChain && !isreindex &&
+ !indexInfo->ii_Concurrent)
{
Oid indexId = RelationGetRelid(indexRelation);
Relation pg_index;
@@ -2409,6 +2421,65 @@ validate_index_heapscan(Relation heapRelation,
/*
+ * index_set_state_flags - adjust pg_index state flags
+ *
+ * This is used during CREATE INDEX CONCURRENTLY to adjust the pg_index
+ * flags that denote the index's state. We must use an in-place update of
+ * the pg_index tuple, because we do not have exclusive lock on the parent
+ * table and so other sessions might concurrently be doing SnapshotNow scans
+ * of pg_index to identify the table's indexes. A transactional update would
+ * risk somebody not seeing the index at all. Because the update is not
+ * transactional and will not roll back on error, this must only be used as
+ * the last step in a transaction that has not made any transactional catalog
+ * updates!
+ *
+ * Note that heap_inplace_update does send a cache inval message for the
+ * tuple, so other sessions will hear about the update as soon as we commit.
+ */
+void
+index_set_state_flags(Oid indexId, IndexStateFlagsAction action)
+{
+ Relation pg_index;
+ HeapTuple indexTuple;
+ Form_pg_index indexForm;
+
+ /* Assert that current xact hasn't done any transactional updates */
+ Assert(GetTopTransactionIdIfAny() == InvalidTransactionId);
+
+ /* Open pg_index and fetch a writable copy of the index's tuple */
+ pg_index = heap_open(IndexRelationId, RowExclusiveLock);
+
+ indexTuple = SearchSysCacheCopy1(INDEXRELID,
+ ObjectIdGetDatum(indexId));
+ if (!HeapTupleIsValid(indexTuple))
+ elog(ERROR, "cache lookup failed for index %u", indexId);
+ indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
+
+ /* Perform the requested state change on the copy */
+ switch (action)
+ {
+ case INDEX_CREATE_SET_READY:
+ /* Set indisready during a CREATE INDEX CONCURRENTLY sequence */
+ Assert(!indexForm->indisready);
+ Assert(!indexForm->indisvalid);
+ indexForm->indisready = true;
+ break;
+ case INDEX_CREATE_SET_VALID:
+ /* Set indisvalid during a CREATE INDEX CONCURRENTLY sequence */
+ Assert(indexForm->indisready);
+ Assert(!indexForm->indisvalid);
+ indexForm->indisvalid = true;
+ break;
+ }
+
+ /* ... and write it back in-place */
+ heap_inplace_update(pg_index, indexTuple);
+
+ heap_close(pg_index, RowExclusiveLock);
+}
+
+
+/*
* IndexGetRelation: given an index's relation OID, get the OID of the
* relation it is an index on. Uses the system cache.
*/
@@ -2437,12 +2508,9 @@ void
reindex_index(Oid indexId, bool skip_constraint_checks)
{
Relation iRel,
- heapRelation,
- pg_index;
+ heapRelation;
Oid heapId;
IndexInfo *indexInfo;
- HeapTuple indexTuple;
- Form_pg_index indexForm;
volatile bool skipped_constraint = false;
/*
@@ -2516,25 +2584,39 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
*
* We can also reset indcheckxmin, because we have now done a
* non-concurrent index build, *except* in the case where index_build
- * found some still-broken HOT chains. If it did, we normally leave
- * indcheckxmin alone (note that index_build won't have changed it,
- * because this is a reindex). But if the index was invalid or not ready
- * and there were broken HOT chains, it seems best to force indcheckxmin
- * true, because the normal argument that the HOT chains couldn't conflict
- * with the index is suspect for an invalid index.
+ * found some still-broken HOT chains. If it did, and we don't have to
+ * change any of the other flags, we just leave indcheckxmin alone (note
+ * that index_build won't have changed it, because this is a reindex).
+ * This is okay and desirable because not updating the tuple leaves the
+ * index's usability horizon (recorded as the tuple's xmin value) the same
+ * as it was.
*
- * Note that it is important to not update the pg_index entry if we don't
- * have to, because updating it will move the index's usability horizon
- * (recorded as the tuple's xmin value) if indcheckxmin is true. We don't
- * really want REINDEX to move the usability horizon forward ever, but we
- * have no choice if we are to fix indisvalid or indisready. Of course,
- * clearing indcheckxmin eliminates the issue, so we're happy to do that
- * if we can. Another reason for caution here is that while reindexing
- * pg_index itself, we must not try to update it. We assume that
- * pg_index's indexes will always have these flags in their clean state.
+ * But, if the index was invalid/not-ready and there were broken HOT
+ * chains, we had better force indcheckxmin true, because the normal
+ * argument that the HOT chains couldn't conflict with the index is
+ * suspect for an invalid index. In this case advancing the usability
+ * horizon is appropriate.
+ *
+ * Note that if we have to update the tuple, there is a risk of concurrent
+ * transactions not seeing it during their SnapshotNow scans of pg_index.
+ * While not especially desirable, this is safe because no such
+ * transaction could be trying to update the table (since we have
+ * ShareLock on it). The worst case is that someone might transiently
+ * fail to use the index for a query --- but it was probably unusable
+ * before anyway, if we are updating the tuple.
+ *
+ * Another reason for avoiding unnecessary updates here is that while
+ * reindexing pg_index itself, we must not try to update tuples in it.
+ * pg_index's indexes should always have these flags in their clean state,
+ * so that won't happen.
*/
if (!skipped_constraint)
{
+ Relation pg_index;
+ HeapTuple indexTuple;
+ Form_pg_index indexForm;
+ bool index_bad;
+
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
indexTuple = SearchSysCacheCopy1(INDEXRELID,
@@ -2543,17 +2625,28 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
elog(ERROR, "cache lookup failed for index %u", indexId);
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
- if (!indexForm->indisvalid || !indexForm->indisready ||
+ index_bad = (!indexForm->indisvalid ||
+ !indexForm->indisready);
+ if (index_bad ||
(indexForm->indcheckxmin && !indexInfo->ii_BrokenHotChain))
{
if (!indexInfo->ii_BrokenHotChain)
indexForm->indcheckxmin = false;
- else if (!indexForm->indisvalid || !indexForm->indisready)
+ else if (index_bad)
indexForm->indcheckxmin = true;
indexForm->indisvalid = true;
indexForm->indisready = true;
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
CatalogUpdateIndexes(pg_index, indexTuple);
+
+ /*
+ * Invalidate the relcache for the table, so that after we commit
+ * all sessions will refresh the table's index list. This ensures
+ * that if anyone misses seeing the pg_index row during this
+ * update, they'll refresh their list before attempting any update
+ * on the table.
+ */
+ CacheInvalidateRelcache(heapRelation);
}
heap_close(pg_index, RowExclusiveLock);