"Kevin Grittner" <[email protected]> wrote:
> I haven't dug into ALTER INDEX enough to know whether it can ever
> cause an index to be rebuilt. If so, we need to treat it like
> DROP INDEX and REINDEX -- which should change all predicate locks
> of any granularity on the index into relation locks on the
> associated table.
>
> CLUSTER or an ALTER TABLE which causes a rewrite should change all
> predicate locks on the table and all indexes into relation locks
> on the associated table. (Obviously, an existing relation lock on
> the table doesn't require any action.)
>
> TRUNCATE TABLE and DROP TABLE should generate a rw-conflict *in*
> to the enclosing transaction (if it is serializable) from all
> transactions holding predicate locks on the table or its indexes.
> Note that this could cause a transactions which is running one of
> these statements to roll back with a serialization error. This
> seems correct to me, since these operations essentially delete all
> rows. If you don't want the potential rollback, these operations
> should be run at another isolation level. The difference between
> these two statements is that I think that TRUNCATE TABLE should
> also move the existing predicate locks to relation locks on the
> table while DROP TABLE (for obvious reasons) should just delete
> the predicate locks.
>
> DROP DATABASE should quietly clean up any predicate locks from
> committed transactions which haven't yet hit their cleanup point
> because of overlapping transactions in other databases.
I missed VACUUM FULL when pulling together the above, but I haven't
found any other omissions. (Still happy to hear about any that
anyone can spot.)
I notice that most of these need to shift transfer locks to relation
locks on the heap, either for a single index or for the heap and all
indexes. I wrote a function to do this and called it from one place
to be able to test it. Consider this a WIP patch on which I would
appreciate review while I work on finding the other places to call
it and the miscellaneous other fixes needed.
Note that I had to expose one previously-static function from
index.c to find the heap OID from the index OID. Also, I ran
pgindent against predicate.c, as I generally like to do when I
modify much code, and it found four comment blocks in predicate.c
touched since the recent global pgindent run which it re-wrapped.
I can manually exclude those from the final patch if people would
prefer that; but if people can ignore those whitespace tweaks, it
might not be all bad to get standard formatting onto them at this
point.
-Kevin
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 54,59 ****
--- 54,60 ----
#include "parser/parser.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
+ #include "storage/predicate.h"
#include "storage/procarray.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
***************
*** 114,120 **** static void validate_index_heapscan(Relation heapRelation,
IndexInfo *indexInfo,
Snapshot snapshot,
v_i_state *state);
- static Oid IndexGetRelation(Oid indexId);
static void SetReindexProcessing(Oid heapOid, Oid indexOid);
static void ResetReindexProcessing(void);
static void SetReindexPending(List *indexes);
--- 115,120 ----
***************
*** 2721,2727 **** validate_index_heapscan(Relation heapRelation,
* IndexGetRelation: given an index's relation OID, get the OID of the
* relation it is an index on. Uses the system cache.
*/
! static Oid
IndexGetRelation(Oid indexId)
{
HeapTuple tuple;
--- 2721,2727 ----
* IndexGetRelation: given an index's relation OID, get the OID of the
* relation it is an index on. Uses the system cache.
*/
! Oid
IndexGetRelation(Oid indexId)
{
HeapTuple tuple;
***************
*** 2782,2787 **** reindex_index(Oid indexId, bool skip_constraint_checks)
--- 2782,2793 ----
*/
CheckTableNotInUse(iRel, "REINDEX INDEX");
+ /*
+ * All predicate locks on the index are about to be made invalid.
+ * Promote them to relation locks on the heap.
+ */
+ TransferPredicateLocksToHeapRelation(iRel);
+
PG_TRY();
{
/* Suppress use of the target index while rebuilding it */
*** a/src/backend/storage/lmgr/predicate.c
--- b/src/backend/storage/lmgr/predicate.c
***************
*** 185,190 ****
--- 185,191 ----
#include "access/twophase.h"
#include "access/twophase_rmgr.h"
#include "access/xact.h"
+ #include "catalog/index.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/predicate.h"
***************
*** 975,982 **** InitPredicateLocks(void)
bool found;
/*
! * Compute size of predicate lock target hashtable.
! * Note these calculations must agree with PredicateLockShmemSize!
*/
max_table_size = NPREDICATELOCKTARGETENTS();
--- 976,983 ----
bool found;
/*
! * Compute size of predicate lock target hashtable. Note these
! * calculations must agree with PredicateLockShmemSize!
*/
max_table_size = NPREDICATELOCKTARGETENTS();
***************
*** 1028,1035 **** InitPredicateLocks(void)
hash_flags);
/*
! * Compute size for serializable transaction hashtable.
! * Note these calculations must agree with PredicateLockShmemSize!
*/
max_table_size = (MaxBackends + max_prepared_xacts);
--- 1029,1036 ----
hash_flags);
/*
! * Compute size for serializable transaction hashtable. Note these
! * calculations must agree with PredicateLockShmemSize!
*/
max_table_size = (MaxBackends + max_prepared_xacts);
***************
*** 2276,2288 **** PredicateLockTupleRowVersionLink(const Relation relation,
newxmin;
/*
! * Bail out quickly if there are no serializable transactions
! * running.
*
! * It's safe to do this check without taking any additional
! * locks. Even if a serializable transaction starts concurrently,
! * we know it can't take any SIREAD locks on the modified tuple
! * because the caller is holding the associated buffer page lock.
*/
if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
return;
--- 2277,2288 ----
newxmin;
/*
! * Bail out quickly if there are no serializable transactions running.
*
! * It's safe to do this check without taking any additional locks. Even
if
! * a serializable transaction starts concurrently, we know it can't take
! * any SIREAD locks on the modified tuple because the caller is holding
! * the associated buffer page lock.
*/
if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
return;
***************
*** 2622,2627 **** exit:
--- 2622,2839 ----
return !outOfShmem;
}
+ /*
+ * For all connections, transfer all predicate locks for the given relation
+ * to a single relation lock on the heap. For a heap relation that includes
+ * all locks on indexes; for an index it is just the locks on that one
+ * index.
+ *
+ * This requires grabbing a lot of LW locks and scanning the entire lock
+ * target table for matches. That makes this more expensive than most
+ * functions, but it will only be called for DDL type commands and there are
+ * fast returns when no serializable transactions are active or the relation
+ * is temporary.
+ *
+ * We are not using the existing TransferPredicateLocksToNewTarget because
+ * it acquires its own locks on the partitions of the two targets invovled,
+ * and we'll already be holding all partition locks.
+ *
+ * We can't throw an error from here, because the call could be from a
+ * transaction which is not serializable.
+ */
+ void
+ TransferPredicateLocksToHeapRelation(const Relation relation)
+ {
+ HASH_SEQ_STATUS seqstat;
+ PREDICATELOCKTARGET *oldtarget;
+ PREDICATELOCKTARGET *heaptarget;
+ PREDICATELOCKTARGETTAG heaptargettag;
+ PREDICATELOCKTAG newpredlocktag;
+ Oid dbId;
+ Oid indexId;
+ Oid heapId;
+ int i;
+ bool isSingleIndex;
+ bool found;
+ uint32 reservedtargettaghash;
+ uint32 heaptargettaghash;
+
+ /*
+ * Bail out quickly if there are no serializable transactions running.
As
+ * with PredicateLockTupleRowVersionLink, it's safe to check this
without
+ * taking locks because the caller is holding the buffer page lock.
+ */
+ if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
+ return;
+
+ if (SkipSplitTracking(relation))
+ return;
+
+ dbId = relation->rd_node.dbNode;
+ if (relation->rd_index == NULL)
+ {
+ isSingleIndex = false;
+ indexId = InvalidOid; /* quiet compiler warning */
+ heapId = relation->rd_id;
+ }
+ else
+ {
+ isSingleIndex = true;
+ indexId = relation->rd_id;
+ heapId = relation->rd_index->indrelid;
+ }
+ Assert(heapId != InvalidOid);
+ SET_PREDICATELOCKTARGETTAG_RELATION(heaptargettag,
+
dbId,
+
heapId);
+ heaptargettaghash = PredicateLockTargetTagHashCode(&heaptargettag);
+ heaptarget = NULL; /* Retrieve first time needed,
then keep. */
+
+ LWLockAcquire(SerializablePredicateLockListLock, LW_EXCLUSIVE);
+ for (i = 0; i < NUM_PREDICATELOCK_PARTITIONS; i++)
+ LWLockAcquire(FirstPredicateLockMgrLock + i, LW_EXCLUSIVE);
+ LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+
+ /*
+ * Remove the reserved entry to give us scratch space, so we know we'll
be
+ * able to create the new lock target.
+ */
+ reservedtargettaghash =
PredicateLockTargetTagHashCode(&ReservedTargetTag);
+ hash_search_with_hash_value(PredicateLockTargetHash,
+
&ReservedTargetTag,
+
reservedtargettaghash,
+ HASH_REMOVE,
&found);
+ Assert(found);
+
+ /* Scan through PredicateLockHash and copy contents */
+ hash_seq_init(&seqstat, PredicateLockTargetHash);
+
+ while ((oldtarget = (PREDICATELOCKTARGET *) hash_seq_search(&seqstat)))
+ {
+ PREDICATELOCK *oldpredlock;
+
+ /*
+ * Check whether this is a target which needs attention.
+ */
+ if (GET_PREDICATELOCKTARGETTAG_DB(oldtarget->tag) != dbId)
+ continue; /* wrong database */
+ if (isSingleIndex)
+ {
+ if (GET_PREDICATELOCKTARGETTAG_RELATION(oldtarget->tag)
!= indexId)
+ continue; /* not the index we're
looking for */
+ }
+ else
+ {
+ if (GET_PREDICATELOCKTARGETTAG_RELATION(oldtarget->tag)
== heapId)
+ {
+ if
(GET_PREDICATELOCKTARGETTAG_TYPE(oldtarget->tag) == PREDLOCKTAG_RELATION)
+ continue; /* already the right
lock */
+ }
+ else
+ {
+ /* This is an index. Is it for the right heap?
*/
+ if
(IndexGetRelation(GET_PREDICATELOCKTARGETTAG_RELATION(oldtarget->tag))
+ != heapId)
+ continue; /* index on wrong heap
relation */
+ }
+ }
+
+ /*
+ * If we made it here, we have work to do. We make sure
the heap
+ * relation lock exists, then we walk the list of predicate
locks for
+ * the old target we found, moving all locks to the heap
relation lock
+ * -- unless they already hold that.
+ */
+
+ /*
+ * First make sure we have the heap relation target. We only
need to
+ * do this once.
+ */
+ if (heaptarget == NULL)
+ {
+ heaptarget =
hash_search_with_hash_value(PredicateLockTargetHash,
+
&heaptargettag,
+
heaptargettaghash,
+
HASH_ENTER, &found);
+ Assert(heaptarget != NULL);
+ if (!found)
+ SHMQueueInit(&heaptarget->predicateLocks);
+ newpredlocktag.myTarget = heaptarget;
+ }
+
+ /*
+ * Loop through moving locks from this target to the relation
target.
+ */
+ oldpredlock = (PREDICATELOCK *)
+ SHMQueueNext(&(oldtarget->predicateLocks),
+ &(oldtarget->predicateLocks),
+ offsetof(PREDICATELOCK,
targetLink));
+ while (oldpredlock)
+ {
+ PREDICATELOCK *nextpredlock;
+ PREDICATELOCK *newpredlock;
+ SerCommitSeqNo oldCommitSeqNo =
oldpredlock->commitSeqNo;
+
+ nextpredlock = (PREDICATELOCK *)
+ SHMQueueNext(&(oldtarget->predicateLocks),
+
&(oldpredlock->targetLink),
+
offsetof(PREDICATELOCK, targetLink));
+ newpredlocktag.myXact = oldpredlock->tag.myXact;
+
+ SHMQueueDelete(&(oldpredlock->xactLink));
+ /* No need for retail delete from oldtarget list. */
+ hash_search(PredicateLockHash,
+ &oldpredlock->tag,
+ HASH_REMOVE, &found);
+ Assert(found);
+
+ newpredlock = (PREDICATELOCK *)
+ hash_search_with_hash_value
+ (PredicateLockHash,
+ &newpredlocktag,
+
PredicateLockHashCodeFromTargetHashCode(&newpredlocktag,
+
heaptargettaghash),
+ HASH_ENTER_NULL, &found);
+ Assert(newpredlock != NULL);
+ if (!found)
+ {
+
SHMQueueInsertBefore(&(heaptarget->predicateLocks),
+
&(newpredlock->targetLink));
+
SHMQueueInsertBefore(&(newpredlocktag.myXact->predicateLocks),
+
&(newpredlock->xactLink));
+ newpredlock->commitSeqNo = oldCommitSeqNo;
+ }
+ else
+ {
+ if (newpredlock->commitSeqNo < oldCommitSeqNo)
+ newpredlock->commitSeqNo =
oldCommitSeqNo;
+ }
+
+ Assert(newpredlock->commitSeqNo != 0);
+ Assert((newpredlock->commitSeqNo ==
InvalidSerCommitSeqNo)
+ || (newpredlock->tag.myXact ==
OldCommittedSxact));
+
+ oldpredlock = nextpredlock;
+ }
+
+ hash_search(PredicateLockTargetHash, &oldtarget->tag,
HASH_REMOVE, &found);
+ Assert(found);
+ }
+
+ /* Put the reserved entry back */
+ hash_search_with_hash_value(PredicateLockTargetHash,
+
&ReservedTargetTag,
+
reservedtargettaghash,
+ HASH_ENTER,
&found);
+ Assert(!found);
+
+ /* Release locks in reverse order */
+ LWLockRelease(SerializableXactHashLock);
+ for (i = NUM_PREDICATELOCK_PARTITIONS - 1; i >= 0; i--)
+ LWLockRelease(FirstPredicateLockMgrLock + i);
+ LWLockRelease(SerializablePredicateLockListLock);
+ }
+
/*
* PredicateLockPageSplit
***************
*** 2646,2655 **** PredicateLockPageSplit(const Relation relation, const
BlockNumber oldblkno,
bool success;
/*
! * Bail out quickly if there are no serializable transactions
! * running. As with PredicateLockTupleRowVersionLink, it's safe to
! * check this without taking locks because the caller is holding
! * the buffer page lock.
*/
if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
return;
--- 2858,2866 ----
bool success;
/*
! * Bail out quickly if there are no serializable transactions running.
As
! * with PredicateLockTupleRowVersionLink, it's safe to check this
without
! * taking locks because the caller is holding the buffer page lock.
*/
if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
return;
*** a/src/include/catalog/index.h
--- b/src/include/catalog/index.h
***************
*** 66,71 **** extern void index_drop(Oid indexId);
--- 66,73 ----
extern IndexInfo *BuildIndexInfo(Relation index);
+ extern Oid IndexGetRelation(Oid indexId);
+
extern void FormIndexDatum(IndexInfo *indexInfo,
TupleTableSlot *slot,
EState *estate,
*** a/src/include/storage/predicate.h
--- b/src/include/storage/predicate.h
***************
*** 50,55 **** extern void PredicateLockTuple(const Relation relation, const
HeapTuple tuple);
--- 50,56 ----
extern void PredicateLockTupleRowVersionLink(const Relation relation, const
HeapTuple oldTuple, const HeapTuple newTuple);
extern void PredicateLockPageSplit(const Relation relation, const BlockNumber
oldblkno, const BlockNumber newblkno);
extern void PredicateLockPageCombine(const Relation relation, const
BlockNumber oldblkno, const BlockNumber newblkno);
+ extern void TransferPredicateLocksToHeapRelation(const Relation relation);
extern void ReleasePredicateLocks(const bool isCommit);
/* conflict detection (may also trigger rollback) */
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers