mirror of
https://github.com/postgres/postgres.git
synced 2025-07-18 17:42:25 +03:00
Fix assorted bugs in CREATE INDEX CONCURRENTLY.
This patch changes CREATE INDEX CONCURRENTLY so that the pg_index flag changes it makes without exclusive lock on the index are made via heap_inplace_update() rather than a normal transactional update. The latter is not very safe because moving the pg_index tuple could result in concurrent SnapshotNow scans finding it twice or not at all, thus possibly resulting in index corruption. In addition, fix various places in the code that ought to check to make sure that the indexes they are manipulating are valid and/or ready as appropriate. These represent bugs that have existed since 8.2, since a failed CREATE INDEX CONCURRENTLY could leave a corrupt or invalid index behind, and we ought not try to do anything that might fail with such an index. Also fix RelationReloadIndexInfo to ensure it copies all the pg_index columns that are allowed to change after initial creation. Previously we could have been left with stale values of some fields in an index relcache entry. It's not clear whether this actually had any user-visible consequences, but it's at least a bug waiting to happen. This is a subset of a patch already applied in 9.2 and HEAD. Back-patch into all earlier supported branches. Tom Lane and Andres Freund
This commit is contained in:
@ -380,6 +380,12 @@ from the index, as well as ensuring that no one can see any inconsistent
|
|||||||
rows in a broken HOT chain (the first condition is stronger than the
|
rows in a broken HOT chain (the first condition is stronger than the
|
||||||
second). Finally, we can mark the index valid for searches.
|
second). Finally, we can mark the index valid for searches.
|
||||||
|
|
||||||
|
Note that we do not need to set pg_index.indcheckxmin in this code path,
|
||||||
|
because we have outwaited any transactions that would need to avoid using
|
||||||
|
the index. (indcheckxmin is only needed because non-concurrent CREATE
|
||||||
|
INDEX doesn't want to wait; its stronger lock would create too much risk of
|
||||||
|
deadlock if it did.)
|
||||||
|
|
||||||
|
|
||||||
Limitations and Restrictions
|
Limitations and Restrictions
|
||||||
----------------------------
|
----------------------------
|
||||||
|
@ -1126,7 +1126,7 @@ BuildIndexInfo(Relation index)
|
|||||||
|
|
||||||
/* other info */
|
/* other info */
|
||||||
ii->ii_Unique = indexStruct->indisunique;
|
ii->ii_Unique = indexStruct->indisunique;
|
||||||
ii->ii_ReadyForInserts = indexStruct->indisready;
|
ii->ii_ReadyForInserts = IndexIsReady(indexStruct);
|
||||||
|
|
||||||
/* initialize index-build state to default */
|
/* initialize index-build state to default */
|
||||||
ii->ii_Concurrent = false;
|
ii->ii_Concurrent = false;
|
||||||
@ -1465,8 +1465,20 @@ index_build(Relation heapRelation,
|
|||||||
* index's usability horizon. Moreover, we *must not* try to change
|
* index's usability horizon. Moreover, we *must not* try to change
|
||||||
* the index's pg_index entry while reindexing pg_index itself, and this
|
* the index's pg_index entry while reindexing pg_index itself, and this
|
||||||
* optimization nicely prevents that.
|
* optimization nicely prevents that.
|
||||||
|
*
|
||||||
|
* 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)
|
if (indexInfo->ii_BrokenHotChain && !isreindex &&
|
||||||
|
!indexInfo->ii_Concurrent)
|
||||||
{
|
{
|
||||||
Oid indexId = RelationGetRelid(indexRelation);
|
Oid indexId = RelationGetRelid(indexRelation);
|
||||||
Relation pg_index;
|
Relation pg_index;
|
||||||
@ -2408,6 +2420,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
|
* IndexGetRelation: given an index's relation OID, get the OID of the
|
||||||
* relation it is an index on. Uses the system cache.
|
* relation it is an index on. Uses the system cache.
|
||||||
@ -2437,12 +2508,9 @@ void
|
|||||||
reindex_index(Oid indexId, bool skip_constraint_checks)
|
reindex_index(Oid indexId, bool skip_constraint_checks)
|
||||||
{
|
{
|
||||||
Relation iRel,
|
Relation iRel,
|
||||||
heapRelation,
|
heapRelation;
|
||||||
pg_index;
|
|
||||||
Oid heapId;
|
Oid heapId;
|
||||||
IndexInfo *indexInfo;
|
IndexInfo *indexInfo;
|
||||||
HeapTuple indexTuple;
|
|
||||||
Form_pg_index indexForm;
|
|
||||||
volatile bool skipped_constraint = false;
|
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
|
* We can also reset indcheckxmin, because we have now done a
|
||||||
* non-concurrent index build, *except* in the case where index_build
|
* non-concurrent index build, *except* in the case where index_build
|
||||||
* found some still-broken HOT chains. If it did, we normally leave
|
* found some still-broken HOT chains. If it did, and we don't have to
|
||||||
* indcheckxmin alone (note that index_build won't have changed it,
|
* change any of the other flags, we just leave indcheckxmin alone (note
|
||||||
* because this is a reindex). But if the index was invalid or not ready
|
* that index_build won't have changed it, because this is a reindex).
|
||||||
* and there were broken HOT chains, it seems best to force indcheckxmin
|
* This is okay and desirable because not updating the tuple leaves the
|
||||||
* true, because the normal argument that the HOT chains couldn't conflict
|
* index's usability horizon (recorded as the tuple's xmin value) the same
|
||||||
* with the index is suspect for an invalid index.
|
* as it was.
|
||||||
*
|
*
|
||||||
* Note that it is important to not update the pg_index entry if we don't
|
* But, if the index was invalid/not-ready and there were broken HOT
|
||||||
* have to, because updating it will move the index's usability horizon
|
* chains, we had better force indcheckxmin true, because the normal
|
||||||
* (recorded as the tuple's xmin value) if indcheckxmin is true. We don't
|
* argument that the HOT chains couldn't conflict with the index is
|
||||||
* really want REINDEX to move the usability horizon forward ever, but we
|
* suspect for an invalid index. In this case advancing the usability
|
||||||
* have no choice if we are to fix indisvalid or indisready. Of course,
|
* horizon is appropriate.
|
||||||
* clearing indcheckxmin eliminates the issue, so we're happy to do that
|
*
|
||||||
* if we can. Another reason for caution here is that while reindexing
|
* Note that if we have to update the tuple, there is a risk of concurrent
|
||||||
* pg_index itself, we must not try to update it. We assume that
|
* transactions not seeing it during their SnapshotNow scans of pg_index.
|
||||||
* pg_index's indexes will always have these flags in their clean state.
|
* 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)
|
if (!skipped_constraint)
|
||||||
{
|
{
|
||||||
|
Relation pg_index;
|
||||||
|
HeapTuple indexTuple;
|
||||||
|
Form_pg_index indexForm;
|
||||||
|
bool index_bad;
|
||||||
|
|
||||||
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
|
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
|
||||||
|
|
||||||
indexTuple = SearchSysCacheCopy1(INDEXRELID,
|
indexTuple = SearchSysCacheCopy1(INDEXRELID,
|
||||||
@ -2543,17 +2625,28 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
|
|||||||
elog(ERROR, "cache lookup failed for index %u", indexId);
|
elog(ERROR, "cache lookup failed for index %u", indexId);
|
||||||
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
|
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))
|
(indexForm->indcheckxmin && !indexInfo->ii_BrokenHotChain))
|
||||||
{
|
{
|
||||||
if (!indexInfo->ii_BrokenHotChain)
|
if (!indexInfo->ii_BrokenHotChain)
|
||||||
indexForm->indcheckxmin = false;
|
indexForm->indcheckxmin = false;
|
||||||
else if (!indexForm->indisvalid || !indexForm->indisready)
|
else if (index_bad)
|
||||||
indexForm->indcheckxmin = true;
|
indexForm->indcheckxmin = true;
|
||||||
indexForm->indisvalid = true;
|
indexForm->indisvalid = true;
|
||||||
indexForm->indisready = true;
|
indexForm->indisready = true;
|
||||||
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
|
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
|
||||||
CatalogUpdateIndexes(pg_index, 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);
|
heap_close(pg_index, RowExclusiveLock);
|
||||||
|
@ -487,7 +487,7 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck)
|
|||||||
* might put recently-dead tuples out-of-order in the new table, and there
|
* might put recently-dead tuples out-of-order in the new table, and there
|
||||||
* is little harm in that.)
|
* is little harm in that.)
|
||||||
*/
|
*/
|
||||||
if (!OldIndex->rd_index->indisvalid)
|
if (!IndexIsValid(OldIndex->rd_index))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
errmsg("cannot cluster on invalid index \"%s\"",
|
errmsg("cannot cluster on invalid index \"%s\"",
|
||||||
@ -501,6 +501,11 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck)
|
|||||||
* mark_index_clustered: mark the specified index as the one clustered on
|
* mark_index_clustered: mark the specified index as the one clustered on
|
||||||
*
|
*
|
||||||
* With indexOid == InvalidOid, will mark all indexes of rel not-clustered.
|
* With indexOid == InvalidOid, will mark all indexes of rel not-clustered.
|
||||||
|
*
|
||||||
|
* Note: we do transactional updates of the pg_index rows, which are unsafe
|
||||||
|
* against concurrent SnapshotNow scans of pg_index. Therefore this is unsafe
|
||||||
|
* to execute with less than full exclusive lock on the parent table;
|
||||||
|
* otherwise concurrent executions of RelationGetIndexList could miss indexes.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
mark_index_clustered(Relation rel, Oid indexOid)
|
mark_index_clustered(Relation rel, Oid indexOid)
|
||||||
@ -556,6 +561,9 @@ mark_index_clustered(Relation rel, Oid indexOid)
|
|||||||
}
|
}
|
||||||
else if (thisIndexOid == indexOid)
|
else if (thisIndexOid == indexOid)
|
||||||
{
|
{
|
||||||
|
/* this was checked earlier, but let's be real sure */
|
||||||
|
if (!IndexIsValid(indexForm))
|
||||||
|
elog(ERROR, "cannot cluster on invalid index %u", indexOid);
|
||||||
indexForm->indisclustered = true;
|
indexForm->indisclustered = true;
|
||||||
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
|
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
|
||||||
CatalogUpdateIndexes(pg_index, indexTuple);
|
CatalogUpdateIndexes(pg_index, indexTuple);
|
||||||
|
@ -147,9 +147,6 @@ DefineIndex(RangeVar *heapRelation,
|
|||||||
LockRelId heaprelid;
|
LockRelId heaprelid;
|
||||||
LOCKTAG heaplocktag;
|
LOCKTAG heaplocktag;
|
||||||
Snapshot snapshot;
|
Snapshot snapshot;
|
||||||
Relation pg_index;
|
|
||||||
HeapTuple indexTuple;
|
|
||||||
Form_pg_index indexForm;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -595,23 +592,7 @@ DefineIndex(RangeVar *heapRelation,
|
|||||||
* commit this transaction, any new transactions that open the table must
|
* commit this transaction, any new transactions that open the table must
|
||||||
* insert new entries into the index for insertions and non-HOT updates.
|
* insert new entries into the index for insertions and non-HOT updates.
|
||||||
*/
|
*/
|
||||||
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
|
index_set_state_flags(indexRelationId, INDEX_CREATE_SET_READY);
|
||||||
|
|
||||||
indexTuple = SearchSysCacheCopy1(INDEXRELID,
|
|
||||||
ObjectIdGetDatum(indexRelationId));
|
|
||||||
if (!HeapTupleIsValid(indexTuple))
|
|
||||||
elog(ERROR, "cache lookup failed for index %u", indexRelationId);
|
|
||||||
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
|
|
||||||
|
|
||||||
Assert(!indexForm->indisready);
|
|
||||||
Assert(!indexForm->indisvalid);
|
|
||||||
|
|
||||||
indexForm->indisready = true;
|
|
||||||
|
|
||||||
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
|
|
||||||
CatalogUpdateIndexes(pg_index, indexTuple);
|
|
||||||
|
|
||||||
heap_close(pg_index, RowExclusiveLock);
|
|
||||||
|
|
||||||
/* we can do away with our snapshot */
|
/* we can do away with our snapshot */
|
||||||
PopActiveSnapshot();
|
PopActiveSnapshot();
|
||||||
@ -735,23 +716,7 @@ DefineIndex(RangeVar *heapRelation,
|
|||||||
/*
|
/*
|
||||||
* Index can now be marked valid -- update its pg_index entry
|
* Index can now be marked valid -- update its pg_index entry
|
||||||
*/
|
*/
|
||||||
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
|
index_set_state_flags(indexRelationId, INDEX_CREATE_SET_VALID);
|
||||||
|
|
||||||
indexTuple = SearchSysCacheCopy1(INDEXRELID,
|
|
||||||
ObjectIdGetDatum(indexRelationId));
|
|
||||||
if (!HeapTupleIsValid(indexTuple))
|
|
||||||
elog(ERROR, "cache lookup failed for index %u", indexRelationId);
|
|
||||||
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
|
|
||||||
|
|
||||||
Assert(indexForm->indisready);
|
|
||||||
Assert(!indexForm->indisvalid);
|
|
||||||
|
|
||||||
indexForm->indisvalid = true;
|
|
||||||
|
|
||||||
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
|
|
||||||
CatalogUpdateIndexes(pg_index, indexTuple);
|
|
||||||
|
|
||||||
heap_close(pg_index, RowExclusiveLock);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The pg_index update will cause backends (including this one) to update
|
* The pg_index update will cause backends (including this one) to update
|
||||||
@ -759,7 +724,7 @@ DefineIndex(RangeVar *heapRelation,
|
|||||||
* relcache inval on the parent table to force replanning of cached plans.
|
* relcache inval on the parent table to force replanning of cached plans.
|
||||||
* Otherwise existing sessions might fail to use the new index where it
|
* Otherwise existing sessions might fail to use the new index where it
|
||||||
* would be useful. (Note that our earlier commits did not create reasons
|
* would be useful. (Note that our earlier commits did not create reasons
|
||||||
* to replan; relcache flush on the index itself was sufficient.)
|
* to replan; so relcache flush on the index itself was sufficient.)
|
||||||
*/
|
*/
|
||||||
CacheInvalidateRelcacheByRelid(heaprelid.relId);
|
CacheInvalidateRelcacheByRelid(heaprelid.relId);
|
||||||
|
|
||||||
|
@ -3882,6 +3882,8 @@ ATExecDropNotNull(Relation rel, const char *colName)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Check that the attribute is not in a primary key
|
* Check that the attribute is not in a primary key
|
||||||
|
*
|
||||||
|
* Note: we'll throw error even if the pkey index is not valid.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Loop over all indexes on the relation */
|
/* Loop over all indexes on the relation */
|
||||||
@ -5064,7 +5066,7 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
|
|||||||
/*
|
/*
|
||||||
* Get the list of index OIDs for the table from the relcache, and look up
|
* Get the list of index OIDs for the table from the relcache, and look up
|
||||||
* each one in the pg_index syscache until we find one marked primary key
|
* each one in the pg_index syscache until we find one marked primary key
|
||||||
* (hopefully there isn't more than one such).
|
* (hopefully there isn't more than one such). Insist it's valid, too.
|
||||||
*/
|
*/
|
||||||
*indexOid = InvalidOid;
|
*indexOid = InvalidOid;
|
||||||
|
|
||||||
@ -5078,7 +5080,7 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
|
|||||||
if (!HeapTupleIsValid(indexTuple))
|
if (!HeapTupleIsValid(indexTuple))
|
||||||
elog(ERROR, "cache lookup failed for index %u", indexoid);
|
elog(ERROR, "cache lookup failed for index %u", indexoid);
|
||||||
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
|
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
|
||||||
if (indexStruct->indisprimary)
|
if (indexStruct->indisprimary && IndexIsValid(indexStruct))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Refuse to use a deferrable primary key. This is per SQL spec,
|
* Refuse to use a deferrable primary key. This is per SQL spec,
|
||||||
@ -5176,10 +5178,12 @@ transformFkeyCheckAttrs(Relation pkrel,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Must have the right number of columns; must be unique and not a
|
* Must have the right number of columns; must be unique and not a
|
||||||
* partial index; forget it if there are any expressions, too
|
* partial index; forget it if there are any expressions, too. Invalid
|
||||||
|
* indexes are out as well.
|
||||||
*/
|
*/
|
||||||
if (indexStruct->indnatts == numattrs &&
|
if (indexStruct->indnatts == numattrs &&
|
||||||
indexStruct->indisunique &&
|
indexStruct->indisunique &&
|
||||||
|
IndexIsValid(indexStruct) &&
|
||||||
heap_attisnull(indexTuple, Anum_pg_index_indpred) &&
|
heap_attisnull(indexTuple, Anum_pg_index_indpred) &&
|
||||||
heap_attisnull(indexTuple, Anum_pg_index_indexprs))
|
heap_attisnull(indexTuple, Anum_pg_index_indexprs))
|
||||||
{
|
{
|
||||||
|
@ -1071,9 +1071,16 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Open all the indexes of the given relation, obtaining the specified kind
|
* Open all the vacuumable indexes of the given relation, obtaining the
|
||||||
* of lock on each. Return an array of Relation pointers for the indexes
|
* specified kind of lock on each. Return an array of Relation pointers for
|
||||||
* into *Irel, and the number of indexes into *nindexes.
|
* the indexes into *Irel, and the number of indexes into *nindexes.
|
||||||
|
*
|
||||||
|
* We consider an index vacuumable if it is marked insertable (IndexIsReady).
|
||||||
|
* If it isn't, probably a CREATE INDEX CONCURRENTLY command failed early in
|
||||||
|
* execution, and what we have is too corrupt to be processable. We will
|
||||||
|
* vacuum even if the index isn't indisvalid; this is important because in a
|
||||||
|
* unique index, uniqueness checks will be performed anyway and had better not
|
||||||
|
* hit dangling index pointers.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
vac_open_indexes(Relation relation, LOCKMODE lockmode,
|
vac_open_indexes(Relation relation, LOCKMODE lockmode,
|
||||||
@ -1087,21 +1094,30 @@ vac_open_indexes(Relation relation, LOCKMODE lockmode,
|
|||||||
|
|
||||||
indexoidlist = RelationGetIndexList(relation);
|
indexoidlist = RelationGetIndexList(relation);
|
||||||
|
|
||||||
*nindexes = list_length(indexoidlist);
|
/* allocate enough memory for all indexes */
|
||||||
|
i = list_length(indexoidlist);
|
||||||
|
|
||||||
if (*nindexes > 0)
|
if (i > 0)
|
||||||
*Irel = (Relation *) palloc(*nindexes * sizeof(Relation));
|
*Irel = (Relation *) palloc(i * sizeof(Relation));
|
||||||
else
|
else
|
||||||
*Irel = NULL;
|
*Irel = NULL;
|
||||||
|
|
||||||
|
/* collect just the ready indexes */
|
||||||
i = 0;
|
i = 0;
|
||||||
foreach(indexoidscan, indexoidlist)
|
foreach(indexoidscan, indexoidlist)
|
||||||
{
|
{
|
||||||
Oid indexoid = lfirst_oid(indexoidscan);
|
Oid indexoid = lfirst_oid(indexoidscan);
|
||||||
|
Relation indrel;
|
||||||
|
|
||||||
(*Irel)[i++] = index_open(indexoid, lockmode);
|
indrel = index_open(indexoid, lockmode);
|
||||||
|
if (IndexIsReady(indrel->rd_index))
|
||||||
|
(*Irel)[i++] = indrel;
|
||||||
|
else
|
||||||
|
index_close(indrel, lockmode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*nindexes = i;
|
||||||
|
|
||||||
list_free(indexoidlist);
|
list_free(indexoidlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -903,6 +903,9 @@ ExecOpenIndices(ResultRelInfo *resultRelInfo)
|
|||||||
/*
|
/*
|
||||||
* For each index, open the index relation and save pg_index info. We
|
* For each index, open the index relation and save pg_index info. We
|
||||||
* acquire RowExclusiveLock, signifying we will update the index.
|
* acquire RowExclusiveLock, signifying we will update the index.
|
||||||
|
*
|
||||||
|
* Note: we do this even if the index is not IndexIsReady; it's not worth
|
||||||
|
* the trouble to optimize for the case where it isn't.
|
||||||
*/
|
*/
|
||||||
i = 0;
|
i = 0;
|
||||||
foreach(l, indexoidlist)
|
foreach(l, indexoidlist)
|
||||||
|
@ -159,9 +159,10 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
|||||||
* Ignore invalid indexes, since they can't safely be used for
|
* Ignore invalid indexes, since they can't safely be used for
|
||||||
* queries. Note that this is OK because the data structure we
|
* queries. Note that this is OK because the data structure we
|
||||||
* are constructing is only used by the planner --- the executor
|
* are constructing is only used by the planner --- the executor
|
||||||
* still needs to insert into "invalid" indexes!
|
* still needs to insert into "invalid" indexes, if they're marked
|
||||||
|
* IndexIsReady.
|
||||||
*/
|
*/
|
||||||
if (!index->indisvalid)
|
if (!IndexIsValid(index))
|
||||||
{
|
{
|
||||||
index_close(indexRelation, NoLock);
|
index_close(indexRelation, NoLock);
|
||||||
continue;
|
continue;
|
||||||
|
20
src/backend/utils/cache/relcache.c
vendored
20
src/backend/utils/cache/relcache.c
vendored
@ -1770,9 +1770,21 @@ RelationReloadIndexInfo(Relation relation)
|
|||||||
RelationGetRelid(relation));
|
RelationGetRelid(relation));
|
||||||
index = (Form_pg_index) GETSTRUCT(tuple);
|
index = (Form_pg_index) GETSTRUCT(tuple);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Basically, let's just copy all the bool fields. There are one or
|
||||||
|
* two of these that can't actually change in the current code, but
|
||||||
|
* it's not worth it to track exactly which ones they are. None of
|
||||||
|
* the array fields are allowed to change, though.
|
||||||
|
*/
|
||||||
|
relation->rd_index->indisunique = index->indisunique;
|
||||||
|
relation->rd_index->indisprimary = index->indisprimary;
|
||||||
|
relation->rd_index->indimmediate = index->indimmediate;
|
||||||
|
relation->rd_index->indisclustered = index->indisclustered;
|
||||||
relation->rd_index->indisvalid = index->indisvalid;
|
relation->rd_index->indisvalid = index->indisvalid;
|
||||||
relation->rd_index->indcheckxmin = index->indcheckxmin;
|
relation->rd_index->indcheckxmin = index->indcheckxmin;
|
||||||
relation->rd_index->indisready = index->indisready;
|
relation->rd_index->indisready = index->indisready;
|
||||||
|
|
||||||
|
/* Copy xmin too, as that is needed to make sense of indcheckxmin */
|
||||||
HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data,
|
HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data,
|
||||||
HeapTupleHeaderGetXmin(tuple->t_data));
|
HeapTupleHeaderGetXmin(tuple->t_data));
|
||||||
|
|
||||||
@ -3380,7 +3392,8 @@ RelationGetIndexList(Relation relation)
|
|||||||
result = insert_ordered_oid(result, index->indexrelid);
|
result = insert_ordered_oid(result, index->indexrelid);
|
||||||
|
|
||||||
/* Check to see if it is a unique, non-partial btree index on OID */
|
/* Check to see if it is a unique, non-partial btree index on OID */
|
||||||
if (index->indnatts == 1 &&
|
if (IndexIsValid(index) &&
|
||||||
|
index->indnatts == 1 &&
|
||||||
index->indisunique && index->indimmediate &&
|
index->indisunique && index->indimmediate &&
|
||||||
index->indkey.values[0] == ObjectIdAttributeNumber &&
|
index->indkey.values[0] == ObjectIdAttributeNumber &&
|
||||||
index->indclass.values[0] == OID_BTREE_OPS_OID &&
|
index->indclass.values[0] == OID_BTREE_OPS_OID &&
|
||||||
@ -3687,6 +3700,11 @@ RelationGetIndexAttrBitmap(Relation relation)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* For each index, add referenced attributes to indexattrs.
|
* For each index, add referenced attributes to indexattrs.
|
||||||
|
*
|
||||||
|
* Note: we consider all indexes returned by RelationGetIndexList, even if
|
||||||
|
* they are not indisready or indisvalid. This is important because an
|
||||||
|
* index for which CREATE INDEX CONCURRENTLY has just started must be
|
||||||
|
* included in HOT-safety decisions (see README.HOT).
|
||||||
*/
|
*/
|
||||||
indexattrs = NULL;
|
indexattrs = NULL;
|
||||||
foreach(l, indexoidlist)
|
foreach(l, indexoidlist)
|
||||||
|
@ -27,6 +27,13 @@ typedef void (*IndexBuildCallback) (Relation index,
|
|||||||
bool tupleIsAlive,
|
bool tupleIsAlive,
|
||||||
void *state);
|
void *state);
|
||||||
|
|
||||||
|
/* Action code for index_set_state_flags */
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
INDEX_CREATE_SET_READY,
|
||||||
|
INDEX_CREATE_SET_VALID
|
||||||
|
} IndexStateFlagsAction;
|
||||||
|
|
||||||
|
|
||||||
extern Oid index_create(Oid heapRelationId,
|
extern Oid index_create(Oid heapRelationId,
|
||||||
const char *indexRelationName,
|
const char *indexRelationName,
|
||||||
@ -71,6 +78,8 @@ extern double IndexBuildHeapScan(Relation heapRelation,
|
|||||||
|
|
||||||
extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
|
extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
|
||||||
|
|
||||||
|
extern void index_set_state_flags(Oid indexId, IndexStateFlagsAction action);
|
||||||
|
|
||||||
extern void reindex_index(Oid indexId, bool skip_constraint_checks);
|
extern void reindex_index(Oid indexId, bool skip_constraint_checks);
|
||||||
|
|
||||||
#define REINDEX_CHECK_CONSTRAINTS 0x1
|
#define REINDEX_CHECK_CONSTRAINTS 0x1
|
||||||
|
@ -88,4 +88,12 @@ typedef FormData_pg_index *Form_pg_index;
|
|||||||
#define INDOPTION_DESC 0x0001 /* values are in reverse order */
|
#define INDOPTION_DESC 0x0001 /* values are in reverse order */
|
||||||
#define INDOPTION_NULLS_FIRST 0x0002 /* NULLs are first instead of last */
|
#define INDOPTION_NULLS_FIRST 0x0002 /* NULLs are first instead of last */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use of these macros is recommended over direct examination of the state
|
||||||
|
* flag columns where possible; this allows source code compatibility with
|
||||||
|
* 9.2 and up.
|
||||||
|
*/
|
||||||
|
#define IndexIsValid(indexForm) ((indexForm)->indisvalid)
|
||||||
|
#define IndexIsReady(indexForm) ((indexForm)->indisready)
|
||||||
|
|
||||||
#endif /* PG_INDEX_H */
|
#endif /* PG_INDEX_H */
|
||||||
|
Reference in New Issue
Block a user