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:
@ -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
|
||||
* is little harm in that.)
|
||||
*/
|
||||
if (!OldIndex->rd_index->indisvalid)
|
||||
if (!IndexIsValid(OldIndex->rd_index))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
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
|
||||
*
|
||||
* 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
|
||||
mark_index_clustered(Relation rel, Oid indexOid)
|
||||
@ -556,6 +561,9 @@ mark_index_clustered(Relation rel, Oid 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;
|
||||
simple_heap_update(pg_index, &indexTuple->t_self, indexTuple);
|
||||
CatalogUpdateIndexes(pg_index, indexTuple);
|
||||
|
@ -147,9 +147,6 @@ DefineIndex(RangeVar *heapRelation,
|
||||
LockRelId heaprelid;
|
||||
LOCKTAG heaplocktag;
|
||||
Snapshot snapshot;
|
||||
Relation pg_index;
|
||||
HeapTuple indexTuple;
|
||||
Form_pg_index indexForm;
|
||||
int i;
|
||||
|
||||
/*
|
||||
@ -595,23 +592,7 @@ DefineIndex(RangeVar *heapRelation,
|
||||
* commit this transaction, any new transactions that open the table must
|
||||
* insert new entries into the index for insertions and non-HOT updates.
|
||||
*/
|
||||
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
|
||||
|
||||
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);
|
||||
index_set_state_flags(indexRelationId, INDEX_CREATE_SET_READY);
|
||||
|
||||
/* we can do away with our snapshot */
|
||||
PopActiveSnapshot();
|
||||
@ -735,23 +716,7 @@ DefineIndex(RangeVar *heapRelation,
|
||||
/*
|
||||
* Index can now be marked valid -- update its pg_index entry
|
||||
*/
|
||||
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
|
||||
|
||||
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);
|
||||
index_set_state_flags(indexRelationId, INDEX_CREATE_SET_VALID);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* Otherwise existing sessions might fail to use the new index where it
|
||||
* 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);
|
||||
|
||||
|
@ -3882,6 +3882,8 @@ ATExecDropNotNull(Relation rel, const char *colName)
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
@ -5064,7 +5066,7 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
|
||||
/*
|
||||
* 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
|
||||
* (hopefully there isn't more than one such).
|
||||
* (hopefully there isn't more than one such). Insist it's valid, too.
|
||||
*/
|
||||
*indexOid = InvalidOid;
|
||||
|
||||
@ -5078,7 +5080,7 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
|
||||
if (!HeapTupleIsValid(indexTuple))
|
||||
elog(ERROR, "cache lookup failed for index %u", indexoid);
|
||||
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,
|
||||
@ -5176,10 +5178,12 @@ transformFkeyCheckAttrs(Relation pkrel,
|
||||
|
||||
/*
|
||||
* 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 &&
|
||||
indexStruct->indisunique &&
|
||||
IndexIsValid(indexStruct) &&
|
||||
heap_attisnull(indexTuple, Anum_pg_index_indpred) &&
|
||||
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
|
||||
* of lock on each. Return an array of Relation pointers for the indexes
|
||||
* into *Irel, and the number of indexes into *nindexes.
|
||||
* Open all the vacuumable indexes of the given relation, obtaining the
|
||||
* specified kind of lock on each. Return an array of Relation pointers for
|
||||
* 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
|
||||
vac_open_indexes(Relation relation, LOCKMODE lockmode,
|
||||
@ -1087,21 +1094,30 @@ vac_open_indexes(Relation relation, LOCKMODE lockmode,
|
||||
|
||||
indexoidlist = RelationGetIndexList(relation);
|
||||
|
||||
*nindexes = list_length(indexoidlist);
|
||||
/* allocate enough memory for all indexes */
|
||||
i = list_length(indexoidlist);
|
||||
|
||||
if (*nindexes > 0)
|
||||
*Irel = (Relation *) palloc(*nindexes * sizeof(Relation));
|
||||
if (i > 0)
|
||||
*Irel = (Relation *) palloc(i * sizeof(Relation));
|
||||
else
|
||||
*Irel = NULL;
|
||||
|
||||
/* collect just the ready indexes */
|
||||
i = 0;
|
||||
foreach(indexoidscan, indexoidlist)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user