📄 cluster.c
字号:
/*------------------------------------------------------------------------- * * cluster.c * CLUSTER a table on an index. * * There is hardly anything left of Paul Brown's original implementation... * * * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.141.2.1 2005/11/22 18:23:06 momjian Exp $ * *------------------------------------------------------------------------- */#include "postgres.h"#include "access/genam.h"#include "access/heapam.h"#include "catalog/catalog.h"#include "catalog/dependency.h"#include "catalog/heap.h"#include "catalog/index.h"#include "catalog/indexing.h"#include "catalog/namespace.h"#include "commands/cluster.h"#include "commands/tablecmds.h"#include "miscadmin.h"#include "utils/acl.h"#include "utils/fmgroids.h"#include "utils/inval.h"#include "utils/lsyscache.h"#include "utils/memutils.h"#include "utils/syscache.h"#include "utils/relcache.h"/* * This struct is used to pass around the information on tables to be * clustered. We need this so we can make a list of them when invoked without * a specific table/index pair. */typedef struct{ Oid tableOid; Oid indexOid;} RelToCluster;static void cluster_rel(RelToCluster *rv, bool recheck);static void rebuild_relation(Relation OldHeap, Oid indexOid);static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex);static List *get_tables_to_cluster(MemoryContext cluster_context);/*--------------------------------------------------------------------------- * This cluster code allows for clustering multiple tables at once. Because * of this, we cannot just run everything on a single transaction, or we * would be forced to acquire exclusive locks on all the tables being * clustered, simultaneously --- very likely leading to deadlock. * * To solve this we follow a similar strategy to VACUUM code, * clustering each relation in a separate transaction. For this to work, * we need to: * - provide a separate memory context so that we can pass information in * a way that survives across transactions * - start a new transaction every time a new relation is clustered * - check for validity of the information on to-be-clustered relations, * as someone might have deleted a relation behind our back, or * clustered one on a different index * - end the transaction * * The single-relation case does not have any such overhead. * * We also allow a relation being specified without index. In that case, * the indisclustered bit will be looked up, and an ERROR will be thrown * if there is no index with the bit set. *--------------------------------------------------------------------------- */voidcluster(ClusterStmt *stmt){ if (stmt->relation != NULL) { /* This is the single-relation case. */ Oid tableOid, indexOid = InvalidOid; Relation rel; RelToCluster rvtc; /* Find and lock the table */ rel = heap_openrv(stmt->relation, AccessExclusiveLock); tableOid = RelationGetRelid(rel); /* Check permissions */ if (!pg_class_ownercheck(tableOid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(rel)); if (stmt->indexname == NULL) { ListCell *index; /* We need to find the index that has indisclustered set. */ foreach(index, RelationGetIndexList(rel)) { HeapTuple idxtuple; Form_pg_index indexForm; indexOid = lfirst_oid(index); idxtuple = SearchSysCache(INDEXRELID, ObjectIdGetDatum(indexOid), 0, 0, 0); if (!HeapTupleIsValid(idxtuple)) elog(ERROR, "cache lookup failed for index %u", indexOid); indexForm = (Form_pg_index) GETSTRUCT(idxtuple); if (indexForm->indisclustered) { ReleaseSysCache(idxtuple); break; } ReleaseSysCache(idxtuple); indexOid = InvalidOid; } if (!OidIsValid(indexOid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("there is no previously clustered index for table \"%s\"", stmt->relation->relname))); } else { /* * The index is expected to be in the same namespace as the * relation. */ indexOid = get_relname_relid(stmt->indexname, rel->rd_rel->relnamespace); if (!OidIsValid(indexOid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("index \"%s\" for table \"%s\" does not exist", stmt->indexname, stmt->relation->relname))); } /* All other checks are done in cluster_rel() */ rvtc.tableOid = tableOid; rvtc.indexOid = indexOid; /* close relation, keep lock till commit */ heap_close(rel, NoLock); /* Do the job */ cluster_rel(&rvtc, false); } else { /* * This is the "multi relation" case. We need to cluster all tables * that have some index with indisclustered set. */ MemoryContext cluster_context; List *rvs; ListCell *rv; /* * We cannot run this form of CLUSTER inside a user transaction block; * we'd be holding locks way too long. */ PreventTransactionChain((void *) stmt, "CLUSTER"); /* * Create special memory context for cross-transaction storage. * * Since it is a child of PortalContext, it will go away even in case * of error. */ cluster_context = AllocSetContextCreate(PortalContext, "Cluster", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* * Build the list of relations to cluster. Note that this lives in * cluster_context. */ rvs = get_tables_to_cluster(cluster_context); /* Commit to get out of starting transaction */ CommitTransactionCommand(); /* Ok, now that we've got them all, cluster them one by one */ foreach(rv, rvs) { RelToCluster *rvtc = (RelToCluster *) lfirst(rv); /* Start a new transaction for each relation. */ StartTransactionCommand(); /* functions in indexes may want a snapshot set */ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot()); cluster_rel(rvtc, true); CommitTransactionCommand(); } /* Start a new transaction for the cleanup work. */ StartTransactionCommand(); /* Clean up working storage */ MemoryContextDelete(cluster_context); }}/* * cluster_rel * * This clusters the table by creating a new, clustered table and * swapping the relfilenodes of the new table and the old table, so * the OID of the original table is preserved. Thus we do not lose * GRANT, inheritance nor references to this table (this was a bug * in releases thru 7.3). * * Also create new indexes and swap the filenodes with the old indexes the * same way we do for the relation. Since we are effectively bulk-loading * the new table, it's better to create the indexes afterwards than to fill * them incrementally while we load the table. */static voidcluster_rel(RelToCluster *rvtc, bool recheck){ Relation OldHeap; /* Check for user-requested abort. */ CHECK_FOR_INTERRUPTS(); /* * Since we may open a new transaction for each relation, we have to check * that the relation still is what we think it is. * * If this is a single-transaction CLUSTER, we can skip these tests. We * *must* skip the one on indisclustered since it would reject an attempt * to cluster a not-previously-clustered index. */ if (recheck) { HeapTuple tuple; Form_pg_index indexForm; /* * Check if the relation and index still exist before opening them */ if (!SearchSysCacheExists(RELOID, ObjectIdGetDatum(rvtc->tableOid), 0, 0, 0) || !SearchSysCacheExists(RELOID, ObjectIdGetDatum(rvtc->indexOid), 0, 0, 0)) return; /* Check that the user still owns the relation */ if (!pg_class_ownercheck(rvtc->tableOid, GetUserId())) return; /* * Check that the index is still the one with indisclustered set. */ tuple = SearchSysCache(INDEXRELID, ObjectIdGetDatum(rvtc->indexOid), 0, 0, 0); if (!HeapTupleIsValid(tuple)) return; /* could have gone away... */ indexForm = (Form_pg_index) GETSTRUCT(tuple); if (!indexForm->indisclustered) { ReleaseSysCache(tuple); return; } ReleaseSysCache(tuple); } /* * We grab exclusive access to the target rel and index for the duration * of the transaction. (This is redundant for the single- transaction * case, since cluster() already did it.) The index lock is taken inside * check_index_is_clusterable. */ OldHeap = heap_open(rvtc->tableOid, AccessExclusiveLock); /* Check index is valid to cluster on */ check_index_is_clusterable(OldHeap, rvtc->indexOid, recheck); /* rebuild_relation does all the dirty work */ rebuild_relation(OldHeap, rvtc->indexOid); /* NB: rebuild_relation does heap_close() on OldHeap */}/* * Verify that the specified index is a legitimate index to cluster on * * Side effect: obtains exclusive lock on the index. The caller should * already have exclusive lock on the table, so the index lock is likely * redundant, but it seems best to grab it anyway to ensure the index * definition can't change under us. */voidcheck_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck){ Relation OldIndex; OldIndex = index_open(indexOid); LockRelation(OldIndex, AccessExclusiveLock); /* * Check that index is in fact an index on the given relation */ if (OldIndex->rd_index == NULL || OldIndex->rd_index->indrelid != RelationGetRelid(OldHeap)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not an index for table \"%s\"", RelationGetRelationName(OldIndex), RelationGetRelationName(OldHeap)))); /* * Disallow clustering on incomplete indexes (those that might not index * every row of the relation). We could relax this by making a separate * seqscan pass over the table to copy the missing rows, but that seems * expensive and tedious. */ if (!heap_attisnull(OldIndex->rd_indextuple, Anum_pg_index_indpred)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot cluster on partial index \"%s\"", RelationGetRelationName(OldIndex)))); if (!OldIndex->rd_am->amindexnulls) { AttrNumber colno; /* * If the AM doesn't index nulls, then it's a partial index unless we * can prove all the rows are non-null. Note we only need look at the * first column; multicolumn-capable AMs are *required* to index nulls * in columns after the first. */ colno = OldIndex->rd_index->indkey.values[0]; if (colno > 0) { /* ordinary user attribute */ if (!OldHeap->rd_att->attrs[colno - 1]->attnotnull) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot cluster on index \"%s\" because access method does not handle null values", RelationGetRelationName(OldIndex)), recheck ? errhint("You may be able to work around this by marking column \"%s\" NOT NULL, or use ALTER TABLE ... SET WITHOUT CLUSTER to remove the cluster specification from the table.", NameStr(OldHeap->rd_att->attrs[colno - 1]->attname)) : errhint("You may be able to work around this by marking column \"%s\" NOT NULL.", NameStr(OldHeap->rd_att->attrs[colno - 1]->attname)))); } else if (colno < 0) { /* system column --- okay, always non-null */ } else /* index expression, lose... */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot cluster on expressional index \"%s\" because its index access method does not handle null values", RelationGetRelationName(OldIndex)))); } /* * Disallow clustering system relations. This will definitely NOT work * for shared relations (we have no way to update pg_class rows in other * databases), nor for nailed-in-cache relations (the relfilenode values * for those are hardwired, see relcache.c). It might work for other * system relations, but I ain't gonna risk it. */ if (IsSystemRelation(OldHeap)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("\"%s\" is a system catalog", RelationGetRelationName(OldHeap)))); /* * Don't allow cluster on temp tables of other backends ... their local * buffer manager is not going to cope. */ if (isOtherTempNamespace(RelationGetNamespace(OldHeap))) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot cluster temporary tables of other sessions"))); /* Drop relcache refcnt on OldIndex, but keep lock */ index_close(OldIndex);}/* * mark_index_clustered: mark the specified index as the one clustered on * * With indexOid == InvalidOid, will mark all indexes of rel not-clustered. */voidmark_index_clustered(Relation rel, Oid indexOid){ HeapTuple indexTuple; Form_pg_index indexForm; Relation pg_index; ListCell *index; /* * If the index is already marked clustered, no need to do anything. */ if (OidIsValid(indexOid)) { indexTuple = SearchSysCache(INDEXRELID, ObjectIdGetDatum(indexOid), 0, 0, 0); if (!HeapTupleIsValid(indexTuple)) elog(ERROR, "cache lookup failed for index %u", indexOid); indexForm = (Form_pg_index) GETSTRUCT(indexTuple); if (indexForm->indisclustered) { ReleaseSysCache(indexTuple); return; } ReleaseSysCache(indexTuple); } /* * Check each index of the relation and set/clear the bit as needed. */ pg_index = heap_open(IndexRelationId, RowExclusiveLock); foreach(index, RelationGetIndexList(rel)) { Oid thisIndexOid = lfirst_oid(index); indexTuple = SearchSysCacheCopy(INDEXRELID, ObjectIdGetDatum(thisIndexOid), 0, 0, 0); if (!HeapTupleIsValid(indexTuple)) elog(ERROR, "cache lookup failed for index %u", thisIndexOid); indexForm = (Form_pg_index) GETSTRUCT(indexTuple); /* * Unset the bit if set. We know it's wrong because we checked this * earlier. */ if (indexForm->indisclustered) { indexForm->indisclustered = false;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -