mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
Re-think guts of DROP INDEX CONCURRENTLY.
Concurrent behaviour was flawed when using a two-step process, so add an additional phase of processing to ensure concurrency for both SELECTs and INSERT/UPDATE/DELETEs. Backpatch to 9.2 Andres Freund, tweaked by me
This commit is contained in:
parent
72a4231f0c
commit
2f0e480d02
@ -1316,6 +1316,10 @@ index_drop(Oid indexId, bool concurrent)
|
|||||||
* table lock strong enough to prevent all queries on the table from
|
* table lock strong enough to prevent all queries on the table from
|
||||||
* proceeding until we commit and send out a shared-cache-inval notice
|
* proceeding until we commit and send out a shared-cache-inval notice
|
||||||
* that will make them update their index lists.
|
* that will make them update their index lists.
|
||||||
|
*
|
||||||
|
* In the concurrent case we make sure that nobody can be looking at the
|
||||||
|
* indexes by dropping the index in multiple steps, so we don't need a full
|
||||||
|
* AccessExclusiveLock yet.
|
||||||
*/
|
*/
|
||||||
heapId = IndexGetRelation(indexId, false);
|
heapId = IndexGetRelation(indexId, false);
|
||||||
if (concurrent)
|
if (concurrent)
|
||||||
@ -1336,7 +1340,19 @@ index_drop(Oid indexId, bool concurrent)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Drop Index concurrently is similar in many ways to creating an index
|
* Drop Index concurrently is similar in many ways to creating an index
|
||||||
* concurrently, so some actions are similar to DefineIndex()
|
* concurrently, so some actions are similar to DefineIndex() just in the
|
||||||
|
* reverse order.
|
||||||
|
*
|
||||||
|
* First we unset indisvalid so queries starting afterwards don't use the
|
||||||
|
* index to answer queries anymore. We have to keep indisready = true
|
||||||
|
* so transactions that are still scanning the index can continue to
|
||||||
|
* see valid index contents. E.g. when they are using READ COMMITTED mode,
|
||||||
|
* and another transactions that started later commits makes changes and
|
||||||
|
* commits, they need to see those new tuples in the index.
|
||||||
|
*
|
||||||
|
* After all transactions that could possibly have used it for queries
|
||||||
|
* ended we can unset indisready and wait till nobody could be updating it
|
||||||
|
* anymore.
|
||||||
*/
|
*/
|
||||||
if (concurrent)
|
if (concurrent)
|
||||||
{
|
{
|
||||||
@ -1355,21 +1371,21 @@ index_drop(Oid indexId, bool concurrent)
|
|||||||
elog(ERROR, "cache lookup failed for index %u", indexId);
|
elog(ERROR, "cache lookup failed for index %u", indexId);
|
||||||
indexForm = (Form_pg_index) GETSTRUCT(tuple);
|
indexForm = (Form_pg_index) GETSTRUCT(tuple);
|
||||||
|
|
||||||
indexForm->indisvalid = false; /* make unusable for queries */
|
/*
|
||||||
indexForm->indisready = false; /* make invisible to changes */
|
* If indisready == true we leave it set so the index still gets
|
||||||
|
* maintained by pre-existing transactions. We only need to ensure
|
||||||
|
* that indisvalid is false.
|
||||||
|
*/
|
||||||
|
if (indexForm->indisvalid)
|
||||||
|
{
|
||||||
|
indexForm->indisvalid = false; /* make unusable for new queries */
|
||||||
|
|
||||||
simple_heap_update(indexRelation, &tuple->t_self, tuple);
|
simple_heap_update(indexRelation, &tuple->t_self, tuple);
|
||||||
CatalogUpdateIndexes(indexRelation, tuple);
|
CatalogUpdateIndexes(indexRelation, tuple);
|
||||||
|
}
|
||||||
|
|
||||||
heap_close(indexRelation, RowExclusiveLock);
|
heap_close(indexRelation, RowExclusiveLock);
|
||||||
|
|
||||||
/*
|
|
||||||
* Invalidate the relcache for the table, so that after this
|
|
||||||
* transaction we will refresh the index list. Forgetting just the
|
|
||||||
* index is not enough.
|
|
||||||
*/
|
|
||||||
CacheInvalidateRelcache(userHeapRelation);
|
|
||||||
|
|
||||||
/* save lockrelid and locktag for below, then close but keep locks */
|
/* save lockrelid and locktag for below, then close but keep locks */
|
||||||
heaprelid = userHeapRelation->rd_lockInfo.lockRelId;
|
heaprelid = userHeapRelation->rd_lockInfo.lockRelId;
|
||||||
SET_LOCKTAG_RELATION(heaplocktag, heaprelid.dbId, heaprelid.relId);
|
SET_LOCKTAG_RELATION(heaplocktag, heaprelid.dbId, heaprelid.relId);
|
||||||
@ -1381,8 +1397,8 @@ index_drop(Oid indexId, bool concurrent)
|
|||||||
/*
|
/*
|
||||||
* For a concurrent drop, it's important to make the catalog entries
|
* For a concurrent drop, it's important to make the catalog entries
|
||||||
* visible to other transactions before we drop the index. The index
|
* visible to other transactions before we drop the index. The index
|
||||||
* will be marked not indisvalid, so that no one else tries to either
|
* will be marked not indisvalid, so that no one else tries to use it
|
||||||
* insert into it or use it for queries.
|
* for queries.
|
||||||
*
|
*
|
||||||
* We must commit our current transaction so that the index update
|
* We must commit our current transaction so that the index update
|
||||||
* becomes visible; then start another. Note that all the data
|
* becomes visible; then start another. Note that all the data
|
||||||
@ -1428,6 +1444,66 @@ index_drop(Oid indexId, bool concurrent)
|
|||||||
old_lockholders++;
|
old_lockholders++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we are sure that nobody uses the index for queries, they just
|
||||||
|
* might have it opened for updating it. So now we can unset
|
||||||
|
* indisready and wait till nobody could update the index anymore.
|
||||||
|
*/
|
||||||
|
indexRelation = heap_open(IndexRelationId, RowExclusiveLock);
|
||||||
|
|
||||||
|
userHeapRelation = heap_open(heapId, ShareUpdateExclusiveLock);
|
||||||
|
userIndexRelation = index_open(indexId, ShareUpdateExclusiveLock);
|
||||||
|
|
||||||
|
tuple = SearchSysCacheCopy1(INDEXRELID,
|
||||||
|
ObjectIdGetDatum(indexId));
|
||||||
|
if (!HeapTupleIsValid(tuple))
|
||||||
|
elog(ERROR, "cache lookup failed for index %u", indexId);
|
||||||
|
indexForm = (Form_pg_index) GETSTRUCT(tuple);
|
||||||
|
|
||||||
|
Assert(indexForm->indisvalid == false);
|
||||||
|
if (indexForm->indisready)
|
||||||
|
{
|
||||||
|
indexForm->indisready = false; /* don't update index anymore */
|
||||||
|
|
||||||
|
simple_heap_update(indexRelation, &tuple->t_self, tuple);
|
||||||
|
CatalogUpdateIndexes(indexRelation, tuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
heap_close(indexRelation, RowExclusiveLock);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Close the relations again, though still holding session lock.
|
||||||
|
*/
|
||||||
|
heap_close(userHeapRelation, NoLock);
|
||||||
|
index_close(userIndexRelation, NoLock);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Invalidate the relcache for the table, so that after this
|
||||||
|
* transaction we will refresh the index list. Forgetting just the
|
||||||
|
* index is not enough.
|
||||||
|
*/
|
||||||
|
CacheInvalidateRelcache(userHeapRelation);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Just as with indisvalid = false we need to make sure indisready
|
||||||
|
* is false is visible for everyone.
|
||||||
|
*/
|
||||||
|
CommitTransactionCommand();
|
||||||
|
StartTransactionCommand();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait till everyone that saw indisready = true finished so we can
|
||||||
|
* finally really remove the index. The logic here is the same as
|
||||||
|
* above.
|
||||||
|
*/
|
||||||
|
old_lockholders = GetLockConflicts(&heaplocktag, AccessExclusiveLock);
|
||||||
|
|
||||||
|
while (VirtualTransactionIdIsValid(*old_lockholders))
|
||||||
|
{
|
||||||
|
VirtualXactLock(*old_lockholders, true);
|
||||||
|
old_lockholders++;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Re-open relations to allow us to complete our actions.
|
* Re-open relations to allow us to complete our actions.
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user