summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Paquier2020-01-22 00:49:48 +0000
committerMichael Paquier2020-01-22 00:49:48 +0000
commitd76652edc56f7892806fabf08cad23321fe842de (patch)
tree6848f0395e0d55ca418239e12d88837f5a6e75bc
parentba1dfbe22d3002ff56933ee6ebd26b9bc9be3d86 (diff)
Fix concurrent indexing operations with temporary tables
Attempting to use CREATE INDEX, DROP INDEX or REINDEX with CONCURRENTLY on a temporary relation with ON COMMIT actions triggered unexpected errors because those operations use multiple transactions internally to complete their work. Here is for example one confusing error when using ON COMMIT DELETE ROWS: ERROR: index "foo" already contains data Issues related to temporary relations and concurrent indexing are fixed in this commit by enforcing the non-concurrent path to be taken for temporary relations even if using CONCURRENTLY, transparently to the user. Using a non-concurrent path does not matter in practice as locks cannot be taken on a temporary relation by a session different than the one owning the relation, and the non-concurrent operation is more effective. The problem exists with REINDEX since v12 with the introduction of CONCURRENTLY, and with CREATE/DROP INDEX since CONCURRENTLY exists for those commands. In all supported versions, this caused only confusing error messages to be generated. Note that with REINDEX, it was also possible to issue a REINDEX CONCURRENTLY for a temporary relation owned by a different session, leading to a server crash. The idea to enforce transparently the non-concurrent code path for temporary relations comes originally from Andres Freund. Reported-by: Manuel Rigger Author: Michael Paquier, Heikki Linnakangas Reviewed-by: Andres Freund, Álvaro Herrera, Heikki Linnakangas Discussion: https://postgr.es/m/CA+u7OA6gP7YAeCguyseusYcc=uR8+ypjCcgDDCTzjQ+k6S9ksQ@mail.gmail.com Backpatch-through: 9.4
-rw-r--r--doc/src/sgml/ref/create_index.sgml5
-rw-r--r--doc/src/sgml/ref/drop_index.sgml5
-rw-r--r--src/backend/catalog/index.c9
-rw-r--r--src/backend/commands/indexcmds.c27
-rw-r--r--src/backend/commands/tablecmds.c18
-rw-r--r--src/backend/utils/cache/lsyscache.c22
-rw-r--r--src/include/utils/lsyscache.h1
-rw-r--r--src/test/regress/expected/create_index.out25
-rw-r--r--src/test/regress/sql/create_index.sql25
9 files changed, 129 insertions, 8 deletions
diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml
index 50326cf609..438a1ec214 100644
--- a/doc/src/sgml/ref/create_index.sgml
+++ b/doc/src/sgml/ref/create_index.sgml
@@ -123,6 +123,11 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ <replaceable class="parameter">name</
&mdash; see <xref linkend="SQL-CREATEINDEX-CONCURRENTLY"
endterm="SQL-CREATEINDEX-CONCURRENTLY-title">.
</para>
+ <para>
+ For temporary tables, <command>CREATE INDEX</command> is always
+ non-concurrent, as no other session can access them, and
+ non-concurrent index creation is cheaper.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/drop_index.sgml b/doc/src/sgml/ref/drop_index.sgml
index d66d30edf7..96e5f049f2 100644
--- a/doc/src/sgml/ref/drop_index.sgml
+++ b/doc/src/sgml/ref/drop_index.sgml
@@ -58,6 +58,11 @@ DROP INDEX [ CONCURRENTLY ] [ IF EXISTS ] <replaceable class="PARAMETER">name</r
performed within a transaction block, but
<command>DROP INDEX CONCURRENTLY</> cannot.
</para>
+ <para>
+ For temporary tables, <command>DROP INDEX</command> is always
+ non-concurrent, as no other session can access them, and
+ non-concurrent index drop is cheaper.
+ </para>
</listitem>
</varlistentry>
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 6912809113..968ecc6138 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1337,6 +1337,15 @@ index_drop(Oid indexId, bool concurrent)
LOCKMODE lockmode;
/*
+ * A temporary relation uses a non-concurrent DROP. Other backends can't
+ * access a temporary relation, so there's no harm in grabbing a stronger
+ * lock (see comments in RemoveRelations), and a non-concurrent DROP is
+ * more efficient.
+ */
+ Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+ !concurrent);
+
+ /*
* To drop an index safely, we must grab exclusive lock on its parent
* table. Exclusive lock on the index alone is insufficient because
* another backend might be about to execute a query on the parent table.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ee1fd0d51..64cbfd2d8f 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -302,6 +302,7 @@ DefineIndex(Oid relationId,
bool skip_build,
bool quiet)
{
+ bool concurrent;
char *indexRelationName;
char *accessMethodName;
Oid *typeObjectId;
@@ -331,6 +332,18 @@ DefineIndex(Oid relationId,
int i;
/*
+ * Force non-concurrent build on temporary relations, even if CONCURRENTLY
+ * was requested. Other backends can't access a temporary relation, so
+ * there's no harm in grabbing a stronger lock, and a non-concurrent DROP
+ * is more efficient. Do this before any use of the concurrent option is
+ * done.
+ */
+ if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+ concurrent = true;
+ else
+ concurrent = false;
+
+ /*
* count attributes in index
*/
numberOfAttributes = list_length(stmt->indexParams);
@@ -355,7 +368,7 @@ DefineIndex(Oid relationId,
* relation. To avoid lock upgrade hazards, that lock should be at least
* as strong as the one we take here.
*/
- lockmode = stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock;
+ lockmode = concurrent ? ShareUpdateExclusiveLock : ShareLock;
rel = heap_open(relationId, lockmode);
relationId = RelationGetRelid(rel);
@@ -540,8 +553,8 @@ DefineIndex(Oid relationId,
indexInfo->ii_ExclusionStrats = NULL;
indexInfo->ii_Unique = stmt->unique;
/* In a concurrent build, mark it not-ready-for-inserts */
- indexInfo->ii_ReadyForInserts = !stmt->concurrent;
- indexInfo->ii_Concurrent = stmt->concurrent;
+ indexInfo->ii_ReadyForInserts = !concurrent;
+ indexInfo->ii_Concurrent = concurrent;
indexInfo->ii_BrokenHotChain = false;
typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
@@ -592,7 +605,7 @@ DefineIndex(Oid relationId,
* A valid stmt->oldNode implies that we already have a built form of the
* index. The caller should also decline any index build.
*/
- Assert(!OidIsValid(stmt->oldNode) || (skip_build && !stmt->concurrent));
+ Assert(!OidIsValid(stmt->oldNode) || (skip_build && !concurrent));
/*
* Make the catalog entries for the index, including constraints. Then, if
@@ -606,15 +619,15 @@ DefineIndex(Oid relationId,
coloptions, reloptions, stmt->primary,
stmt->isconstraint, stmt->deferrable, stmt->initdeferred,
allowSystemTableMods,
- skip_build || stmt->concurrent,
- stmt->concurrent, !check_rights);
+ skip_build || concurrent,
+ concurrent, !check_rights);
/* Add any requested comment */
if (stmt->idxcomment != NULL)
CreateComments(indexRelationId, RelationRelationId, 0,
stmt->idxcomment);
- if (!stmt->concurrent)
+ if (!concurrent)
{
/* Close the heap and we're done, in the non-concurrent case */
heap_close(rel, NoLock);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5e48b17628..560d8d0b79 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -784,7 +784,11 @@ RemoveRelations(DropStmt *drop)
/* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */
if (drop->concurrent)
{
- flags |= PERFORM_DELETION_CONCURRENTLY;
+ /*
+ * Note that for temporary relations this lock may get upgraded
+ * later on, but as no other session can access a temporary
+ * relation, this is actually fine.
+ */
lockmode = ShareUpdateExclusiveLock;
Assert(drop->removeType == OBJECT_INDEX);
if (list_length(drop->objects) != 1)
@@ -875,6 +879,18 @@ RemoveRelations(DropStmt *drop)
continue;
}
+ /*
+ * Decide if concurrent mode needs to be used here or not. The
+ * relation persistence cannot be known without its OID.
+ */
+ if (drop->concurrent &&
+ get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+ {
+ Assert(list_length(drop->objects) == 1 &&
+ drop->removeType == OBJECT_INDEX);
+ flags |= PERFORM_DELETION_CONCURRENTLY;
+ }
+
/* OK, we're ready to delete this one */
obj.classId = RelationRelationId;
obj.objectId = relOid;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index ec43636f14..41fad7d7f6 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1797,6 +1797,28 @@ get_rel_tablespace(Oid relid)
return InvalidOid;
}
+/*
+ * get_rel_persistence
+ *
+ * Returns the relpersistence associated with a given relation.
+ */
+char
+get_rel_persistence(Oid relid)
+{
+ HeapTuple tp;
+ Form_pg_class reltup;
+ char result;
+
+ tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ reltup = (Form_pg_class) GETSTRUCT(tp);
+ result = reltup->relpersistence;
+ ReleaseSysCache(tp);
+
+ return result;
+}
+
/* ---------- TYPE CACHE ---------- */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index f46460a575..753151517c 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -103,6 +103,7 @@ extern Oid get_rel_namespace(Oid relid);
extern Oid get_rel_type_id(Oid relid);
extern char get_rel_relkind(Oid relid);
extern Oid get_rel_tablespace(Oid relid);
+extern char get_rel_persistence(Oid relid);
extern bool get_typisdefined(Oid typid);
extern int16 get_typlen(Oid typid);
extern bool get_typbyval(Oid typid);
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 18f3fffcd2..4d16870d08 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2387,6 +2387,31 @@ Indexes:
"concur_index5" btree (f2) WHERE f1 = 'x'::text
"std_index" btree (f2)
+-- Temporary tables with concurrent builds and on-commit actions
+-- CONCURRENTLY used with CREATE INDEX and DROP INDEX is ignored.
+-- PRESERVE ROWS, the default.
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT PRESERVE ROWS;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+DROP INDEX CONCURRENTLY concur_temp_ind;
+DROP TABLE concur_temp;
+-- ON COMMIT DROP
+BEGIN;
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT DROP;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+-- Fails when running in a transaction.
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block
+COMMIT;
+-- ON COMMIT DELETE ROWS
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT DELETE ROWS;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+DROP INDEX CONCURRENTLY concur_temp_ind;
+DROP TABLE concur_temp;
--
-- Try some concurrent index drops
--
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index f826084512..cd5ad8c25c 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -756,6 +756,31 @@ VACUUM FULL concur_heap;
REINDEX TABLE concur_heap;
\d concur_heap
+-- Temporary tables with concurrent builds and on-commit actions
+-- CONCURRENTLY used with CREATE INDEX and DROP INDEX is ignored.
+-- PRESERVE ROWS, the default.
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT PRESERVE ROWS;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+DROP INDEX CONCURRENTLY concur_temp_ind;
+DROP TABLE concur_temp;
+-- ON COMMIT DROP
+BEGIN;
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT DROP;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+-- Fails when running in a transaction.
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+COMMIT;
+-- ON COMMIT DELETE ROWS
+CREATE TEMP TABLE concur_temp (f1 int, f2 text)
+ ON COMMIT DELETE ROWS;
+INSERT INTO concur_temp VALUES (1, 'foo'), (2, 'bar');
+CREATE INDEX CONCURRENTLY concur_temp_ind ON concur_temp(f1);
+DROP INDEX CONCURRENTLY concur_temp_ind;
+DROP TABLE concur_temp;
+
--
-- Try some concurrent index drops
--