On Fri, Dec 30, 2011 at 10:20 PM, Peter Eisentraut <pete...@gmx.net> wrote: > On ons, 2011-08-24 at 11:24 -0700, Daniel Farina wrote: >> I was poking around at tablecmds and index.c and wonder if a similar >> two-pass approach as used by CREATE INDEX CONCURRENTLY can be used to >> create a DROP INDEX CONCURRENTLY, and if there would be any interest >> in accepting such a patch. > > Hmm, it seems I just independently came up with this same concept. My > problem is that if a CREATE INDEX CONCURRENTLY fails, you need an > exclusive lock on the table just to clean that up. If the table is > under constant load, you can't easily do that. So a two-pass DROP INDEX > CONCURRENTLY might have been helpful for me.
Here's a patch for this. Please review. -- Simon Riggs http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Training & Services
diff --git a/doc/src/sgml/ref/drop_index.sgml b/doc/src/sgml/ref/drop_index.sgml index 7177ef2..a5abb5b 100644 --- a/doc/src/sgml/ref/drop_index.sgml +++ b/doc/src/sgml/ref/drop_index.sgml @@ -21,7 +21,9 @@ PostgreSQL documentation <refsynopsisdiv> <synopsis> -DROP INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ CASCADE | RESTRICT ] +DROP INDEX + [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ CASCADE | RESTRICT ] + CONCURRENTLY <replaceable class="PARAMETER">name</replaceable> </synopsis> </refsynopsisdiv> @@ -50,6 +52,30 @@ DROP INDEX [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> [, .. </varlistentry> <varlistentry> + <term><literal>CONCURRENTLY</literal></term> + <listitem> + <para> + When this option is used, <productname>PostgreSQL</> will drop the + index without taking any locks that prevent concurrent selects, inserts, + updates, or deletes on the table; whereas a standard index drop + waits for a lock that locks out everything on the table until it's done. + Concurrent drop index is a two stage process. First, we mark the index + invalid and then commit the change. Next we wait until there are no + users locking the table who can see the invalid index. + </para> + <para> + There are several caveats to be aware of when using this option. + Only one index name can be specified if the <literal>CONCURRENTLY</literal> + parameter is specified. Only one concurrent index drop can occur on a + table at a time and no modifications on the table are allowed meanwhile. + Regular <command>DROP INDEX</> command can be performed within + a transaction block, but <command>DROP INDEX CONCURRENTLY</> cannot. + There is no CASCADE option when dropping an index concurrently. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><replaceable class="PARAMETER">name</replaceable></term> <listitem> <para> diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 223c097..656af21 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -171,8 +171,9 @@ static void reportDependentObjects(const ObjectAddresses *targetObjects, DropBehavior behavior, int msglevel, const ObjectAddress *origObject); -static void deleteOneObject(const ObjectAddress *object, Relation depRel); -static void doDeletion(const ObjectAddress *object); +static void deleteOneObject(const ObjectAddress *object, Relation depRel, + int option_flags); +static void doDeletion(const ObjectAddress *object, int option_flags); static void AcquireDeletionLock(const ObjectAddress *object); static void ReleaseDeletionLock(const ObjectAddress *object); static bool find_expr_references_walker(Node *node, @@ -254,7 +255,7 @@ performDeletion(const ObjectAddress *object, { ObjectAddress *thisobj = targetObjects->refs + i; - deleteOneObject(thisobj, depRel); + deleteOneObject(thisobj, depRel, 0); } /* And clean up */ @@ -276,6 +277,13 @@ void performMultipleDeletions(const ObjectAddresses *objects, DropBehavior behavior) { + performMultipleDeletionsWithOptions(objects, behavior, 0); +} + +void +performMultipleDeletionsWithOptions(const ObjectAddresses *objects, + DropBehavior behavior, int option_flags) +{ Relation depRel; ObjectAddresses *targetObjects; int i; @@ -336,13 +344,17 @@ performMultipleDeletions(const ObjectAddresses *objects, { ObjectAddress *thisobj = targetObjects->refs + i; - deleteOneObject(thisobj, depRel); + deleteOneObject(thisobj, depRel, option_flags); } /* And clean up */ free_object_addresses(targetObjects); - heap_close(depRel, RowExclusiveLock); + /* + * We closed depRel earlier if doing a drop concurrently + */ + if ((option_flags & OPT_CONCURRENTLY) != OPT_CONCURRENTLY) + heap_close(depRel, RowExclusiveLock); } /* @@ -407,7 +419,7 @@ deleteWhatDependsOn(const ObjectAddress *object, if (thisextra->flags & DEPFLAG_ORIGINAL) continue; - deleteOneObject(thisobj, depRel); + deleteOneObject(thisobj, depRel, 0); } /* And clean up */ @@ -950,7 +962,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects, * depRel is the already-open pg_depend relation. */ static void -deleteOneObject(const ObjectAddress *object, Relation depRel) +deleteOneObject(const ObjectAddress *object, Relation depRel, int option_flags) { ScanKeyData key[3]; int nkeys; @@ -1001,9 +1013,16 @@ deleteOneObject(const ObjectAddress *object, Relation depRel) object->objectSubId); /* + * Close depRel if we are doing a drop concurrently because it + * commits the transaction, so we don't want dangling references. + */ + if ((option_flags & OPT_CONCURRENTLY) == OPT_CONCURRENTLY) + heap_close(depRel, RowExclusiveLock); + + /* * Now delete the object itself, in an object-type-dependent way. */ - doDeletion(object); + doDeletion(object, option_flags); /* * Delete any comments or security labels associated with this object. @@ -1028,7 +1047,7 @@ deleteOneObject(const ObjectAddress *object, Relation depRel) * doDeletion: actually delete a single object */ static void -doDeletion(const ObjectAddress *object) +doDeletion(const ObjectAddress *object, int option_flags) { switch (getObjectClass(object)) { @@ -1038,8 +1057,11 @@ doDeletion(const ObjectAddress *object) if (relKind == RELKIND_INDEX) { + bool concurrent = ((option_flags & OPT_CONCURRENTLY) + == OPT_CONCURRENTLY); + Assert(object->objectSubId == 0); - index_drop(object->objectId); + index_drop(object->objectId, concurrent); } else { diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index f9075c4..b0ddd18 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1282,7 +1282,7 @@ index_constraint_create(Relation heapRelation, * else associated dependencies won't be cleaned up. */ void -index_drop(Oid indexId) +index_drop(Oid indexId, bool concurrent) { Oid heapId; Relation userHeapRelation; @@ -1290,6 +1290,12 @@ index_drop(Oid indexId) Relation indexRelation; HeapTuple tuple; bool hasexprs; + LockRelId heaprelid, + indexrelid; + LOCKTAG heaplocktag, + indexlocktag; + VirtualTransactionId *old_lockholders; + Form_pg_index indexForm; /* * To drop an index safely, we must grab exclusive lock on its parent @@ -1302,9 +1308,16 @@ index_drop(Oid indexId) * that will make them update their index lists. */ heapId = IndexGetRelation(indexId, false); - userHeapRelation = heap_open(heapId, AccessExclusiveLock); - - userIndexRelation = index_open(indexId, AccessExclusiveLock); + if (concurrent) + { + userHeapRelation = heap_open(heapId, ShareUpdateExclusiveLock); + userIndexRelation = index_open(indexId, ShareUpdateExclusiveLock); + } + else + { + userHeapRelation = heap_open(heapId, AccessExclusiveLock); + userIndexRelation = index_open(indexId, AccessExclusiveLock); + } /* * There can no longer be anyone *else* touching the index, but we might @@ -1313,6 +1326,100 @@ index_drop(Oid indexId) CheckTableNotInUse(userIndexRelation, "DROP INDEX"); /* + * Drop Index concurrently is similar in many ways to creating an + * index concurrently, so some actions are similar to DefineIndex() + */ + if (concurrent) + { + /* + * Mark index invalid by updating its pg_index entry + * + * Don't Assert(indexForm->indisvalid) because we may be trying to + * clear up after an error when trying to create an index which left + * the index invalid + */ + indexRelation = heap_open(IndexRelationId, RowExclusiveLock); + + tuple = SearchSysCacheCopy1(INDEXRELID, + ObjectIdGetDatum(indexId)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for index %u", indexId); + indexForm = (Form_pg_index) GETSTRUCT(tuple); + + indexForm->indisvalid = false; + + simple_heap_update(indexRelation, &tuple->t_self, tuple); + CatalogUpdateIndexes(indexRelation, tuple); + + heap_close(indexRelation, RowExclusiveLock); + + /* save lockrelid and locktag for below, then close but keep locks */ + heaprelid = userHeapRelation->rd_lockInfo.lockRelId; + SET_LOCKTAG_RELATION(heaplocktag, heaprelid.dbId, heaprelid.relId); + heap_close(userHeapRelation, NoLock); + + indexrelid = userIndexRelation->rd_lockInfo.lockRelId; + SET_LOCKTAG_RELATION(indexlocktag, indexrelid.dbId, indexrelid.relId); + index_close(userIndexRelation, NoLock); + + /* + * For a concurrent drop, it's important to make the catalog entries + * visible to other transactions before we drop the index. The index + * will be marked not indisvalid, so that no one else tries to either + * insert into it or use it for queries. + * + * We must commit our current transaction so that the index update becomes + * visible; then start another. Note that all the data structures we just + * built are lost in the commit. The only data we keep past here are the + * relation IDs. + * + * Before committing, get a session-level lock on the table, to ensure + * that neither it nor the index can be dropped before we finish. This + * cannot block, even if someone else is waiting for access, because we + * already have the same lock within our transaction. + */ + LockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock); + LockRelationIdForSession(&indexrelid, ShareUpdateExclusiveLock); + + PopActiveSnapshot(); + CommitTransactionCommand(); + StartTransactionCommand(); + + /* + * Now we must wait until no running transaction could have the table open + * with the old list of indexes. To do this, inquire which xacts + * currently would conflict with AccessExclusiveLock on the table -- ie, + * which ones have a lock of any kind on the table. Then wait for each of + * these xacts to commit or abort. Note we do not need to worry about + * xacts that open the table for writing after this point; they will see + * the index as invalid when they open the relation. + * + * Note: the reason we use actual lock acquisition here, rather than just + * checking the ProcArray and sleeping, is that deadlock is possible if + * one of the transactions in question is blocked trying to acquire an + * exclusive lock on our table. The lock code will detect deadlock and + * error out properly. + * + * Note: GetLockConflicts() never reports our own xid, hence we need not + * check for that. Also, prepared xacts are not reported, which is fine + * since they certainly aren't going to do anything more. + */ + 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 + */ + userHeapRelation = heap_open(heapId, ShareUpdateExclusiveLock); + userIndexRelation = index_open(indexId, ShareUpdateExclusiveLock); + } + + /* * All predicate locks on the index are about to be made invalid. Promote * them to relation locks on the heap. */ @@ -1378,6 +1485,15 @@ index_drop(Oid indexId) * Close owning rel, but keep lock */ heap_close(userHeapRelation, NoLock); + + /* + * Release the session locks before we go. + */ + if (concurrent) + { + UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock); + UnlockRelationIdForSession(&indexrelid, ShareUpdateExclusiveLock); + } } /* ---------------------------------------------------------------- diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 4953def..7a15ea4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -239,6 +239,7 @@ struct DropRelationCallbackState { char relkind; Oid heapOid; + bool concurrent; }; /* Alter table target-type flags for ATSimplePermissions */ @@ -730,6 +731,7 @@ RemoveRelations(DropStmt *drop) ObjectAddresses *objects; char relkind; ListCell *cell; + int option_flags = 0; /* * First we identify all the relations, then we delete them in a single @@ -792,7 +794,14 @@ RemoveRelations(DropStmt *drop) /* Look up the appropriate relation using namespace search. */ state.relkind = relkind; state.heapOid = InvalidOid; - relOid = RangeVarGetRelidExtended(rel, AccessExclusiveLock, true, + state.concurrent = drop->concurrent; + if (drop->concurrent) + relOid = RangeVarGetRelidExtended(rel, ShareUpdateExclusiveLock, true, + false, + RangeVarCallbackForDropRelation, + (void *) &state); + else + relOid = RangeVarGetRelidExtended(rel, AccessExclusiveLock, true, false, RangeVarCallbackForDropRelation, (void *) &state); @@ -812,7 +821,20 @@ RemoveRelations(DropStmt *drop) add_exact_object_address(&obj, objects); } - performMultipleDeletions(objects, drop->behavior); + /* + * Set options and check further requirements for concurrent drop + */ + if (drop->concurrent) + { + /* + * Confirm that concurrent behaviour is restricted in grammar. + */ + Assert(drop->removeType == OBJECT_INDEX); + + option_flags |= OPT_CONCURRENTLY; + } + + performMultipleDeletionsWithOptions(objects, drop->behavior, option_flags); free_object_addresses(objects); } @@ -881,7 +903,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, { state->heapOid = IndexGetRelation(relOid, true); if (OidIsValid(state->heapOid)) - LockRelationOid(state->heapOid, AccessExclusiveLock); + LockRelationOid(state->heapOid, + (state->concurrent ? ShareUpdateExclusiveLock: AccessExclusiveLock)); } } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index dd2dd25..025a6c1 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2761,6 +2761,7 @@ _copyDropStmt(const DropStmt *from) COPY_SCALAR_FIELD(removeType); COPY_SCALAR_FIELD(behavior); COPY_SCALAR_FIELD(missing_ok); + COPY_SCALAR_FIELD(concurrent); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index c2fdb2b..42ecaac 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1187,6 +1187,7 @@ _equalDropStmt(const DropStmt *a, const DropStmt *b) COMPARE_SCALAR_FIELD(removeType); COMPARE_SCALAR_FIELD(behavior); COMPARE_SCALAR_FIELD(missing_ok); + COMPARE_SCALAR_FIELD(concurrent); return true; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 8943c5b..35f786d 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -213,7 +213,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt - DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt + DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropIndexStmt DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt @@ -307,7 +307,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, opt_column_list columnList opt_name_list sort_clause opt_sort_clause sortby_list index_params name_list from_clause from_list opt_array_bounds - qualified_name_list any_name any_name_list + qualified_name_list any_name any_name_list any_name_single any_operator expr_list attrs target_list insert_column_list set_target_list set_clause_list set_clause multiple_set_clause @@ -741,6 +741,7 @@ stmt : | DropFdwStmt | DropForeignServerStmt | DropGroupStmt + | DropIndexStmt | DropOpClassStmt | DropOpFamilyStmt | DropOwnedStmt @@ -4751,7 +4752,6 @@ DropStmt: DROP drop_type IF_P EXISTS any_name_list opt_drop_behavior drop_type: TABLE { $$ = OBJECT_TABLE; } | SEQUENCE { $$ = OBJECT_SEQUENCE; } | VIEW { $$ = OBJECT_VIEW; } - | INDEX { $$ = OBJECT_INDEX; } | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; } | TYPE_P { $$ = OBJECT_TYPE; } | DOMAIN_P { $$ = OBJECT_DOMAIN; } @@ -4780,6 +4780,44 @@ attrs: '.' attr_name { $$ = lappend($1, makeString($3)); } ; +any_name_single: + any_name { $$ = list_make1($1); } + ; + +DropIndexStmt: DROP INDEX IF_P EXISTS any_name_list + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_INDEX; + n->missing_ok = TRUE; + n->objects = $5; + n->arguments = NIL; + n->behavior = DROP_RESTRICT; + n->concurrent = false; + $$ = (Node *)n; + } + | DROP INDEX any_name_list + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_INDEX; + n->missing_ok = FALSE; + n->objects = $3; + n->arguments = NIL; + n->behavior = DROP_RESTRICT; + n->concurrent = false; + $$ = (Node *)n; + } + | DROP INDEX CONCURRENTLY any_name_single + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_INDEX; + n->missing_ok = FALSE; + n->objects = $4; + n->arguments = NIL; + n->behavior = DROP_RESTRICT; + n->concurrent = true; + $$ = (Node *)n; + } + ; /***************************************************************************** * diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 36d9edc..61337b8 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -631,10 +631,15 @@ standard_ProcessUtility(Node *parsetree, case T_DropStmt: switch (((DropStmt *) parsetree)->removeType) { + case OBJECT_INDEX: + if (((DropStmt *) parsetree)->concurrent) + PreventTransactionChain(isTopLevel, + "DROP INDEX CONCURRENTLY"); + /* fall through */ + case OBJECT_TABLE: case OBJECT_SEQUENCE: case OBJECT_VIEW: - case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: RemoveRelations((DropStmt *) parsetree); break; diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 5302e50..77a1ede 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -158,6 +158,11 @@ extern void performDeletion(const ObjectAddress *object, extern void performMultipleDeletions(const ObjectAddresses *objects, DropBehavior behavior); +extern void performMultipleDeletionsWithOptions(const ObjectAddresses *objects, + DropBehavior behavior, int option_flags); +/* options specified for object deletion */ +#define OPT_CONCURRENTLY 0x01 + extern void deleteWhatDependsOn(const ObjectAddress *object, bool showNotices); diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 9efd9af..8916980 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -63,7 +63,7 @@ extern void index_constraint_create(Relation heapRelation, bool update_pgindex, bool allow_system_table_mods); -extern void index_drop(Oid indexId); +extern void index_drop(Oid indexId, bool concurrent); extern IndexInfo *BuildIndexInfo(Relation index); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 6e8b110..67612f6 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1905,6 +1905,7 @@ typedef struct DropStmt ObjectType removeType; /* object type */ DropBehavior behavior; /* RESTRICT or CASCADE behavior */ bool missing_ok; /* skip error if object is missing? */ + bool concurrent; /* drop index concurrently? */ } DropStmt; /* ---------------------- diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 18457e0..c0697e5 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -2204,6 +2204,38 @@ Indexes: "concur_index5" btree (f2) WHERE f1 = 'x'::text "std_index" btree (f2) +-- +-- Try some concurrent index drops +-- +DROP INDEX CONCURRENTLY "concur_index2"; +-- failures +DROP INDEX IF EXISTS CONCURRENTLY; +ERROR: syntax error at or near "CONCURRENTLY" +LINE 1: DROP INDEX IF EXISTS CONCURRENTLY; + ^ +DROP INDEX CONCURRENTLY "concur_index2", "concur_index3"; +ERROR: syntax error at or near "," +LINE 1: DROP INDEX CONCURRENTLY "concur_index2", "concur_index3"; + ^ +BEGIN; +DROP INDEX CONCURRENTLY "concur_index5"; +ERROR: DROP INDEX CONCURRENTLY cannot run inside a transaction block +ROLLBACK; +-- successes +DROP INDEX CONCURRENTLY "concur_index3"; +DROP INDEX CONCURRENTLY "concur_index4"; +DROP INDEX CONCURRENTLY "concur_index5"; +DROP INDEX CONCURRENTLY "concur_index1"; +DROP INDEX CONCURRENTLY "concur_heap_expr_idx"; +\d concur_heap +Table "public.concur_heap" + Column | Type | Modifiers +--------+------+----------- + f1 | text | + f2 | text | +Indexes: + "std_index" btree (f2) + DROP TABLE concur_heap; -- -- Test ADD CONSTRAINT USING INDEX diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index 8c60cb6..892f04b 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -695,6 +695,27 @@ COMMIT; \d concur_heap +-- +-- Try some concurrent index drops +-- +DROP INDEX CONCURRENTLY "concur_index2"; + +-- failures +DROP INDEX IF EXISTS CONCURRENTLY; +DROP INDEX CONCURRENTLY "concur_index2", "concur_index3"; +BEGIN; +DROP INDEX CONCURRENTLY "concur_index5"; +ROLLBACK; + +-- successes +DROP INDEX CONCURRENTLY "concur_index3"; +DROP INDEX CONCURRENTLY "concur_index4"; +DROP INDEX CONCURRENTLY "concur_index5"; +DROP INDEX CONCURRENTLY "concur_index1"; +DROP INDEX CONCURRENTLY "concur_heap_expr_idx"; + +\d concur_heap + DROP TABLE concur_heap; --
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers