Commit 2fd8685e authored by Alvaro Herrera's avatar Alvaro Herrera

Simplify check of modified attributes in heap_update

The old coding was getting more complicated as new things were added,
and it would be barely tolerable with upcoming WARM updates and other
future features such as indirect indexes.  The new coding incurs a small
performance cost in synthetic benchmark cases, and is barely measurable
in normal cases.  A much larger benefit is expected from WARM, which
could actually bolt its needs on top of the existing coding, but it is
much uglier and bug-prone than doing it on this new code.  Additional
optimization can be applied on top of this, if need be.

Reviewed-by: Pavan Deolasee, Amit Kapila, Mithun CY
Discussion: https://postgr.es/m/20161228232018.4hc66ndrzpz4g4wn@alvherre.pgsql
	https://postgr.es/m/CABOikdMJfz69dBNRTOZcB6s5A0tf8OMCyQVYQyR-WFFdoEwKMQ@mail.gmail.com
parent 9a095271
...@@ -96,11 +96,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, ...@@ -96,11 +96,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
Buffer newbuf, HeapTuple oldtup, Buffer newbuf, HeapTuple oldtup,
HeapTuple newtup, HeapTuple old_key_tup, HeapTuple newtup, HeapTuple old_key_tup,
bool all_visible_cleared, bool new_all_visible_cleared); bool all_visible_cleared, bool new_all_visible_cleared);
static void HeapSatisfiesHOTandKeyUpdate(Relation relation, static Bitmapset *HeapDetermineModifiedColumns(Relation relation,
Bitmapset *hot_attrs, Bitmapset *interesting_cols,
Bitmapset *key_attrs, Bitmapset *id_attrs,
bool *satisfies_hot, bool *satisfies_key,
bool *satisfies_id,
HeapTuple oldtup, HeapTuple newtup); HeapTuple oldtup, HeapTuple newtup);
static bool heap_acquire_tuplock(Relation relation, ItemPointer tid, static bool heap_acquire_tuplock(Relation relation, ItemPointer tid,
LockTupleMode mode, LockWaitPolicy wait_policy, LockTupleMode mode, LockWaitPolicy wait_policy,
...@@ -3471,6 +3468,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ...@@ -3471,6 +3468,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
Bitmapset *hot_attrs; Bitmapset *hot_attrs;
Bitmapset *key_attrs; Bitmapset *key_attrs;
Bitmapset *id_attrs; Bitmapset *id_attrs;
Bitmapset *interesting_attrs;
Bitmapset *modified_attrs;
ItemId lp; ItemId lp;
HeapTupleData oldtup; HeapTupleData oldtup;
HeapTuple heaptup; HeapTuple heaptup;
...@@ -3488,10 +3487,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ...@@ -3488,10 +3487,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
pagefree; pagefree;
bool have_tuple_lock = false; bool have_tuple_lock = false;
bool iscombo; bool iscombo;
bool satisfies_hot;
bool satisfies_key;
bool satisfies_id;
bool use_hot_update = false; bool use_hot_update = false;
bool hot_attrs_checked = false;
bool key_intact; bool key_intact;
bool all_visible_cleared = false; bool all_visible_cleared = false;
bool all_visible_cleared_new = false; bool all_visible_cleared_new = false;
...@@ -3517,26 +3514,50 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ...@@ -3517,26 +3514,50 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
errmsg("cannot update tuples during a parallel operation"))); errmsg("cannot update tuples during a parallel operation")));
/* /*
* Fetch the list of attributes to be checked for HOT update. This is * Fetch the list of attributes to be checked for various operations.
* wasted effort if we fail to update or have to put the new tuple on a
* different page. But we must compute the list before obtaining buffer
* lock --- in the worst case, if we are doing an update on one of the
* relevant system catalogs, we could deadlock if we try to fetch the list
* later. In any case, the relcache caches the data so this is usually
* pretty cheap.
* *
* Note that we get a copy here, so we need not worry about relcache flush * For HOT considerations, this is wasted effort if we fail to update or
* happening midway through. * have to put the new tuple on a different page. But we must compute the
* list before obtaining buffer lock --- in the worst case, if we are doing
* an update on one of the relevant system catalogs, we could deadlock if
* we try to fetch the list later. In any case, the relcache caches the
* data so this is usually pretty cheap.
*
* We also need columns used by the replica identity and columns that are
* considered the "key" of rows in the table.
*
* Note that we get copies of each bitmap, so we need not worry about
* relcache flush happening midway through.
*/ */
hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL); hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL);
key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY); key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
id_attrs = RelationGetIndexAttrBitmap(relation, id_attrs = RelationGetIndexAttrBitmap(relation,
INDEX_ATTR_BITMAP_IDENTITY_KEY); INDEX_ATTR_BITMAP_IDENTITY_KEY);
block = ItemPointerGetBlockNumber(otid); block = ItemPointerGetBlockNumber(otid);
buffer = ReadBuffer(relation, block); buffer = ReadBuffer(relation, block);
page = BufferGetPage(buffer); page = BufferGetPage(buffer);
interesting_attrs = NULL;
/*
* If the page is already full, there is hardly any chance of doing a HOT
* update on this page. It might be wasteful effort to look for index
* column updates only to later reject HOT updates for lack of space in the
* same page. So we be conservative and only fetch hot_attrs if the page is
* not already full. Since we are already holding a pin on the buffer,
* there is no chance that the buffer can get cleaned up concurrently and
* even if that was possible, in the worst case we lose a chance to do a
* HOT update.
*/
if (!PageIsFull(page))
{
interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
hot_attrs_checked = true;
}
interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
/* /*
* Before locking the buffer, pin the visibility map page if it appears to * Before locking the buffer, pin the visibility map page if it appears to
* be necessary. Since we haven't got the lock yet, someone else might be * be necessary. Since we haven't got the lock yet, someone else might be
...@@ -3552,7 +3573,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ...@@ -3552,7 +3573,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
Assert(ItemIdIsNormal(lp)); Assert(ItemIdIsNormal(lp));
/* /*
* Fill in enough data in oldtup for HeapSatisfiesHOTandKeyUpdate to work * Fill in enough data in oldtup for HeapDetermineModifiedColumns to work
* properly. * properly.
*/ */
oldtup.t_tableOid = RelationGetRelid(relation); oldtup.t_tableOid = RelationGetRelid(relation);
...@@ -3578,6 +3599,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ...@@ -3578,6 +3599,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
Assert(!(newtup->t_data->t_infomask & HEAP_HASOID)); Assert(!(newtup->t_data->t_infomask & HEAP_HASOID));
} }
/* Determine columns modified by the update. */
modified_attrs = HeapDetermineModifiedColumns(relation, interesting_attrs,
&oldtup, newtup);
/* /*
* If we're not updating any "key" column, we can grab a weaker lock type. * If we're not updating any "key" column, we can grab a weaker lock type.
* This allows for more concurrency when we are running simultaneously * This allows for more concurrency when we are running simultaneously
...@@ -3589,10 +3614,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, ...@@ -3589,10 +3614,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
* is updates that don't manipulate key columns, not those that * is updates that don't manipulate key columns, not those that
* serendipitiously arrive at the same key values. * serendipitiously arrive at the same key values.
*/ */
HeapSatisfiesHOTandKeyUpdate(relation, hot_attrs, key_attrs, id_attrs, if (!bms_overlap(modified_attrs, key_attrs))
&satisfies_hot, &satisfies_key,
&satisfies_id, &oldtup, newtup);
if (satisfies_key)
{ {
*lockmode = LockTupleNoKeyExclusive; *lockmode = LockTupleNoKeyExclusive;
mxact_status = MultiXactStatusNoKeyUpdate; mxact_status = MultiXactStatusNoKeyUpdate;
...@@ -3831,6 +3853,8 @@ l2: ...@@ -3831,6 +3853,8 @@ l2:
bms_free(hot_attrs); bms_free(hot_attrs);
bms_free(key_attrs); bms_free(key_attrs);
bms_free(id_attrs); bms_free(id_attrs);
bms_free(modified_attrs);
bms_free(interesting_attrs);
return result; return result;
} }
...@@ -4133,9 +4157,10 @@ l2: ...@@ -4133,9 +4157,10 @@ l2:
/* /*
* Since the new tuple is going into the same page, we might be able * Since the new tuple is going into the same page, we might be able
* to do a HOT update. Check if any of the index columns have been * to do a HOT update. Check if any of the index columns have been
* changed. If not, then HOT update is possible. * changed. If the page was already full, we may have skipped checking
* for index columns. If so, HOT update is possible.
*/ */
if (satisfies_hot) if (hot_attrs_checked && !bms_overlap(modified_attrs, hot_attrs))
use_hot_update = true; use_hot_update = true;
} }
else else
...@@ -4150,7 +4175,9 @@ l2: ...@@ -4150,7 +4175,9 @@ l2:
* ExtractReplicaIdentity() will return NULL if nothing needs to be * ExtractReplicaIdentity() will return NULL if nothing needs to be
* logged. * logged.
*/ */
old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, !satisfies_id, &old_key_copied); old_key_tuple = ExtractReplicaIdentity(relation, &oldtup,
bms_overlap(modified_attrs, id_attrs),
&old_key_copied);
/* NO EREPORT(ERROR) from here till changes are logged */ /* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION(); START_CRIT_SECTION();
...@@ -4298,13 +4325,15 @@ l2: ...@@ -4298,13 +4325,15 @@ l2:
bms_free(hot_attrs); bms_free(hot_attrs);
bms_free(key_attrs); bms_free(key_attrs);
bms_free(id_attrs); bms_free(id_attrs);
bms_free(modified_attrs);
bms_free(interesting_attrs);
return HeapTupleMayBeUpdated; return HeapTupleMayBeUpdated;
} }
/* /*
* Check if the specified attribute's value is same in both given tuples. * Check if the specified attribute's value is same in both given tuples.
* Subroutine for HeapSatisfiesHOTandKeyUpdate. * Subroutine for HeapDetermineModifiedColumns.
*/ */
static bool static bool
heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum, heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
...@@ -4338,7 +4367,7 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum, ...@@ -4338,7 +4367,7 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
/* /*
* Extract the corresponding values. XXX this is pretty inefficient if * Extract the corresponding values. XXX this is pretty inefficient if
* there are many indexed columns. Should HeapSatisfiesHOTandKeyUpdate do * there are many indexed columns. Should HeapDetermineModifiedColumns do
* a single heap_deform_tuple call on each tuple, instead? But that * a single heap_deform_tuple call on each tuple, instead? But that
* doesn't work for system columns ... * doesn't work for system columns ...
*/ */
...@@ -4383,114 +4412,30 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum, ...@@ -4383,114 +4412,30 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum,
/* /*
* Check which columns are being updated. * Check which columns are being updated.
* *
* This simultaneously checks conditions for HOT updates, for FOR KEY * Given an updated tuple, determine (and return into the output bitmapset),
* SHARE updates, and REPLICA IDENTITY concerns. Since much of the time they * from those listed as interesting, the set of columns that changed.
* will be checking very similar sets of columns, and doing the same tests on
* them, it makes sense to optimize and do them together.
*
* We receive three bitmapsets comprising the three sets of columns we're
* interested in. Note these are destructively modified; that is OK since
* this is invoked at most once in heap_update.
* *
* hot_result is set to TRUE if it's okay to do a HOT update (i.e. it does not * The input bitmapset is destructively modified; that is OK since this is
* modified indexed columns); key_result is set to TRUE if the update does not * invoked at most once in heap_update.
* modify columns used in the key; id_result is set to TRUE if the update does
* not modify columns in any index marked as the REPLICA IDENTITY.
*/ */
static void static Bitmapset *
HeapSatisfiesHOTandKeyUpdate(Relation relation, Bitmapset *hot_attrs, HeapDetermineModifiedColumns(Relation relation, Bitmapset *interesting_cols,
Bitmapset *key_attrs, Bitmapset *id_attrs,
bool *satisfies_hot, bool *satisfies_key,
bool *satisfies_id,
HeapTuple oldtup, HeapTuple newtup) HeapTuple oldtup, HeapTuple newtup)
{ {
int next_hot_attnum; int attnum;
int next_key_attnum; Bitmapset *modified = NULL;
int next_id_attnum;
bool hot_result = true;
bool key_result = true;
bool id_result = true;
/* If REPLICA IDENTITY is set to FULL, id_attrs will be empty. */
Assert(bms_is_subset(id_attrs, key_attrs));
Assert(bms_is_subset(key_attrs, hot_attrs));
/*
* If one of these sets contains no remaining bits, bms_first_member will
* return -1, and after adding FirstLowInvalidHeapAttributeNumber (which
* is negative!) we'll get an attribute number that can't possibly be
* real, and thus won't match any actual attribute number.
*/
next_hot_attnum = bms_first_member(hot_attrs);
next_hot_attnum += FirstLowInvalidHeapAttributeNumber;
next_key_attnum = bms_first_member(key_attrs);
next_key_attnum += FirstLowInvalidHeapAttributeNumber;
next_id_attnum = bms_first_member(id_attrs);
next_id_attnum += FirstLowInvalidHeapAttributeNumber;
for (;;) while ((attnum = bms_first_member(interesting_cols)) >= 0)
{ {
bool changed; attnum += FirstLowInvalidHeapAttributeNumber;
int check_now;
/* if (!heap_tuple_attr_equals(RelationGetDescr(relation),
* Since the HOT attributes are a superset of the key attributes and attnum, oldtup, newtup))
* the key attributes are a superset of the id attributes, this logic modified = bms_add_member(modified,
* is guaranteed to identify the next column that needs to be checked. attnum - FirstLowInvalidHeapAttributeNumber);
*/
if (hot_result && next_hot_attnum > FirstLowInvalidHeapAttributeNumber)
check_now = next_hot_attnum;
else if (key_result && next_key_attnum > FirstLowInvalidHeapAttributeNumber)
check_now = next_key_attnum;
else if (id_result && next_id_attnum > FirstLowInvalidHeapAttributeNumber)
check_now = next_id_attnum;
else
break;
/* See whether it changed. */
changed = !heap_tuple_attr_equals(RelationGetDescr(relation),
check_now, oldtup, newtup);
if (changed)
{
if (check_now == next_hot_attnum)
hot_result = false;
if (check_now == next_key_attnum)
key_result = false;
if (check_now == next_id_attnum)
id_result = false;
/* if all are false now, we can stop checking */
if (!hot_result && !key_result && !id_result)
break;
}
/*
* Advance the next attribute numbers for the sets that contain the
* attribute we just checked. As we work our way through the columns,
* the next_attnum values will rise; but when each set becomes empty,
* bms_first_member() will return -1 and the attribute number will end
* up with a value less than FirstLowInvalidHeapAttributeNumber.
*/
if (hot_result && check_now == next_hot_attnum)
{
next_hot_attnum = bms_first_member(hot_attrs);
next_hot_attnum += FirstLowInvalidHeapAttributeNumber;
}
if (key_result && check_now == next_key_attnum)
{
next_key_attnum = bms_first_member(key_attrs);
next_key_attnum += FirstLowInvalidHeapAttributeNumber;
}
if (id_result && check_now == next_id_attnum)
{
next_id_attnum = bms_first_member(id_attrs);
next_id_attnum += FirstLowInvalidHeapAttributeNumber;
}
} }
*satisfies_hot = hot_result; return modified;
*satisfies_key = key_result;
*satisfies_id = id_result;
} }
/* /*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment