Re: ERROR: found unexpected null value in index - Mailing list pgsql-bugs
| From | Tom Lane |
|---|---|
| Subject | Re: ERROR: found unexpected null value in index |
| Date | |
| Msg-id | 12489.1562788970@sss.pgh.pa.us Whole thread Raw |
| In response to | Re: ERROR: found unexpected null value in index (Peter Geoghegan <pg@bowt.ie>) |
| Responses |
Re: ERROR: found unexpected null value in index
|
| List | pgsql-bugs |
Peter Geoghegan <pg@bowt.ie> writes:
> On Tue, Jul 9, 2019 at 7:47 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Hm. So maybe we need to teach it to ignore tuples that are not the tips
>> of their HOT chains?
> An approach like that was the first thing that I thought of, but I'll
> have to study the problem some more before coming up with a firm
> opinion.
I experimented with the attached. It solves the reported problem and
passes check-world. I made it just ignore any tuple for which
HeapTupleHeaderIsHotUpdated is true. It might seem like there's a risk
of ignoring valid data, if the end tuple of a HOT chain is dead due to
a transaction abort, but since HeapTupleHeaderIsHotUpdated checks for
xmax validity I judge that the risk of that is small enough to be
acceptable.
A bigger problem with this is that in the tableam world, this seems
like a complete disaster modularity-wise. I think it won't actually
fail --- non-heap AMs are probably not returning
BufferHeapTupleTableSlots, and even if they are, they shouldn't be
marking tuples HOT_UPDATED unless that concept applies to them.
But it sure seems like this leaves get_actual_variable_range() knowing
way more than it ought to about storage-level concerns.
Should we try to transpose some of this logic to below the AM API,
and if so, what should that look like?
regards, tom lane
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d7e3f09..b51cbed 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5232,8 +5232,6 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
InvalidOid, /* no reg proc for this */
(Datum) 0); /* constant */
- have_data = true;
-
/* If min is requested ... */
if (min)
{
@@ -5262,15 +5260,33 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
* get_actual_variable_range() call will not have to visit
* that heap entry. In this way we avoid repetitive work when
* this function is used a lot during planning.
+ *
+ * However, all is not perfect, because if the index was
+ * recently created it may have entries pointing to broken HOT
+ * chains that contain tuples that don't match the index entry
+ * but aren't yet known-dead either. We need to ignore such
+ * tuples, since they might not be representative of the index
+ * extremal values, indeed could even contain NULLs. We
+ * approximate this by ignoring any hot-updated tuples we see
+ * and continuing to scan.
*/
index_scan = index_beginscan(heapRel, indexRel,
&SnapshotNonVacuumable,
1, 0);
index_rescan(index_scan, scankeys, 1, NULL, 0);
- /* Fetch first tuple in sortop's direction */
- if (index_getnext_slot(index_scan, indexscandir, slot))
+ /* Fetch first/next tuple in sortop's direction */
+ while (index_getnext_slot(index_scan, indexscandir, slot))
{
+ /* Ignore hot-updated tuples, per comment above */
+ if (TTS_IS_BUFFERTUPLE(slot))
+ {
+ BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+ if (HeapTupleHeaderIsHotUpdated(bslot->base.tupdata.t_data))
+ continue;
+ }
+
/* Extract the index column values from the slot */
FormIndexDatum(indexInfo, slot, estate,
values, isnull);
@@ -5284,24 +5300,40 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
MemoryContextSwitchTo(oldcontext);
*min = datumCopy(values[0], typByVal, typLen);
MemoryContextSwitchTo(tmpcontext);
+ have_data = true;
+ break;
}
- else
- have_data = false;
index_endscan(index_scan);
}
+ else
+ {
+ /* If min not requested, assume index has data */
+ have_data = true;
+ }
/* If max is requested, and we didn't find the index is empty */
if (max && have_data)
{
+ have_data = false;
+
index_scan = index_beginscan(heapRel, indexRel,
&SnapshotNonVacuumable,
1, 0);
index_rescan(index_scan, scankeys, 1, NULL, 0);
- /* Fetch first tuple in reverse direction */
- if (index_getnext_slot(index_scan, -indexscandir, slot))
+ /* Fetch first/next tuple in reverse direction */
+ while (index_getnext_slot(index_scan, -indexscandir, slot))
{
+ /* Ignore hot-updated tuples, per comment above */
+ if (TTS_IS_BUFFERTUPLE(slot))
+ {
+ BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+ if (HeapTupleHeaderIsHotUpdated(bslot->base.tupdata.t_data))
+ continue;
+ }
+
/* Extract the index column values from the slot */
FormIndexDatum(indexInfo, slot, estate,
values, isnull);
@@ -5315,9 +5347,9 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
MemoryContextSwitchTo(oldcontext);
*max = datumCopy(values[0], typByVal, typLen);
MemoryContextSwitchTo(tmpcontext);
+ have_data = true;
+ break;
}
- else
- have_data = false;
index_endscan(index_scan);
}
pgsql-bugs by date: