diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index a35b93b..00fc926 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -5245,7 +5245,7 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, HeapTuple tup; Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; - SnapshotData SnapshotDirty; + SnapshotData SnapshotNonVacuumable; estate = CreateExecutorState(); econtext = GetPerTupleExprContext(estate); @@ -5268,7 +5268,7 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRel)); econtext->ecxt_scantuple = slot; get_typlenbyval(vardata->atttype, &typLen, &typByVal); - InitDirtySnapshot(SnapshotDirty); + InitNonVacuumableSnapshot(SnapshotNonVacuumable, heapRel); /* set up an IS NOT NULL scan key so that we ignore nulls */ ScanKeyEntryInitialize(&scankeys[0], @@ -5290,17 +5290,17 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, * active snapshot, which is the best approximation we've got * to what the query will see when executed. But that won't * be exact if a new snap is taken before running the query, - * and it can be very expensive if a lot of uncommitted rows - * exist at the end of the index (because we'll laboriously - * fetch each one and reject it). What seems like a good - * compromise is to use SnapshotDirty. That will accept - * uncommitted rows, and thus avoid fetching multiple heap - * tuples in this scenario. On the other hand, it will reject - * known-dead rows, and thus not give a bogus answer when the - * extreme value has been deleted; that case motivates not - * using SnapshotAny here. + * and it can be very expensive if a lot of uncommitted or dead + * rows exist at the beginning or end of the index (because we'll + * laboriously fetch each one and reject it). What seems like a + * good compromise is to use SnapshotNonVacuumable. That will + * accept uncommitted rows and recently dead rows, and thus avoid + * fetching multiple heap tuples in this scenario. On the other + * hand, it will reject known-dead rows, and thus not give a bogus + * answer when the extreme value has been deleted; that case + * motivates not using SnapshotAny here. */ - index_scan = index_beginscan(heapRel, indexRel, &SnapshotDirty, + index_scan = index_beginscan(heapRel, indexRel, &SnapshotNonVacuumable, 1, 0); index_rescan(index_scan, scankeys, 1, NULL, 0); @@ -5332,7 +5332,7 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, /* If max is requested, and we didn't find the index is empty */ if (max && have_data) { - index_scan = index_beginscan(heapRel, indexRel, &SnapshotDirty, + index_scan = index_beginscan(heapRel, indexRel, &SnapshotNonVacuumable, 1, 0); index_rescan(index_scan, scankeys, 1, NULL, 0); diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c index 519f3b6..3254426 100644 --- a/src/backend/utils/time/tqual.c +++ b/src/backend/utils/time/tqual.c @@ -49,6 +49,9 @@ * visible unless part of interrupted vacuum, used for TOAST * HeapTupleSatisfiesAny() * all tuples are visible + * HeapTupleSatisfiesNonVacuumable() + * all non-vacuumable tuples are visible, i.e all tuples + * except known dead tuples * * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -1392,6 +1395,24 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, return HEAPTUPLE_DEAD; } + +/* + * HeapTupleSatisfiesNonVacuumable + * False if tuple not visible to any transactionn, i.e. dead. + * True in all other cases. + * + * Use result of HeapTupleSatisfiesVacuum and return + * corresponding boolean. + */ +bool +HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot, + Buffer buffer) +{ + return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer) + != HEAPTUPLE_DEAD; +} + + /* * HeapTupleIsSurelyDead * diff --git a/src/include/utils/tqual.h b/src/include/utils/tqual.h index f39f21b..2e2b050 100644 --- a/src/include/utils/tqual.h +++ b/src/include/utils/tqual.h @@ -16,7 +16,9 @@ #define TQUAL_H #include "utils/snapshot.h" +#include "utils/snapmgr.h" #include "access/xlogdefs.h" +#include "catalog/catalog.h" /* Static variables representing various special snapshot semantics */ @@ -68,6 +70,8 @@ extern bool HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot, Buffer buffer); extern bool HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer); +extern bool HeapTupleSatisfiesNonVacuumable(HeapTuple htup, + Snapshot snapshot, Buffer buffer); /* Special "satisfies" routines with different APIs */ extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup, @@ -101,6 +105,26 @@ extern bool ResolveCminCmaxDuringDecoding(struct HTAB *tuplecid_data, ((snapshotdata).satisfies = HeapTupleSatisfiesDirty) /* + * Similarly, some initialization is required for SnapshotNonVacuumable. + * We need to set xmin horizon for this relation. If it's a proper + * catalog relation or a user defined, additional, catalog relation, we + * need to use the horizon that includes slots, otherwise the data-only + * horizon can be used. Note that the toast relation of user defined + * relations are *not* considered catalog relations. + */ +#define InitNonVacuumableSnapshot(snapshotdata, relation) \ + do { \ + if (IsCatalogRelation(relation) || \ + RelationIsAccessibleInLogicalDecoding(relation)) \ + (snapshotdata).xmin = RecentGlobalXmin; \ + else \ + (snapshotdata).xmin = \ + TransactionIdLimitedForOldSnapshots(RecentGlobalDataXmin, \ + relation); \ + (snapshotdata).satisfies = HeapTupleSatisfiesNonVacuumable; \ + } while(0) + +/* * Similarly, some initialization is required for SnapshotToast. We need * to set lsn and whenTaken correctly to support snapshot_too_old. */