diff options
author | Simon Riggs <simon@2ndQuadrant.com> | 2018-03-27 19:57:02 +0100 |
---|---|---|
committer | Simon Riggs <simon@2ndQuadrant.com> | 2018-03-27 19:57:02 +0100 |
commit | c203d6cf81b4d7e43edb2b75ec1b741ba48e04e0 (patch) | |
tree | cf9e4a14290ef99232a5f5f477d5b2672df57629 /src/backend/access/heap | |
parent | 1944cdc98273dbb8439ad9b387ca2858531afcf0 (diff) | |
download | postgresql-c203d6cf81b4d7e43edb2b75ec1b741ba48e04e0.tar.gz postgresql-c203d6cf81b4d7e43edb2b75ec1b741ba48e04e0.zip |
Allow HOT updates for some expression indexes
If the value of an index expression is unchanged after UPDATE,
allow HOT updates where previously we disallowed them, giving
a significant performance boost in those cases.
Particularly useful for indexes such as JSON->>field where the
JSON value changes but the indexed value does not.
Submitted as "surjective indexes" patch, now enabled by use
of new "recheck_on_update" parameter.
Author: Konstantin Knizhnik
Reviewer: Simon Riggs, with much wordsmithing and some cleanup
Diffstat (limited to 'src/backend/access/heap')
-rw-r--r-- | src/backend/access/heap/heapam.c | 105 |
1 files changed, 98 insertions, 7 deletions
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index c08ab14c02b..d7279248e70 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -56,6 +56,7 @@ #include "access/xlogutils.h" #include "catalog/catalog.h" #include "catalog/namespace.h" +#include "catalog/index.h" #include "miscadmin.h" #include "pgstat.h" #include "port/atomics.h" @@ -74,7 +75,9 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/tqual.h" - +#include "utils/memutils.h" +#include "nodes/execnodes.h" +#include "executor/executor.h" /* GUC variable */ bool synchronize_seqscans = true; @@ -126,6 +129,7 @@ static bool ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified, bool *copy); +static bool ProjIndexIsUnchanged(Relation relation, HeapTuple oldtup, HeapTuple newtup); /* @@ -3508,6 +3512,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, HTSU_Result result; TransactionId xid = GetCurrentTransactionId(); Bitmapset *hot_attrs; + Bitmapset *proj_idx_attrs; Bitmapset *key_attrs; Bitmapset *id_attrs; Bitmapset *interesting_attrs; @@ -3571,12 +3576,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, * 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_HOT); + proj_idx_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_PROJ); key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY); id_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY); - - block = ItemPointerGetBlockNumber(otid); buffer = ReadBuffer(relation, block); page = BufferGetPage(buffer); @@ -3596,6 +3600,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, if (!PageIsFull(page)) { interesting_attrs = bms_add_members(interesting_attrs, hot_attrs); + interesting_attrs = bms_add_members(interesting_attrs, proj_idx_attrs); hot_attrs_checked = true; } interesting_attrs = bms_add_members(interesting_attrs, key_attrs); @@ -3894,6 +3899,7 @@ l2: if (vmbuffer != InvalidBuffer) ReleaseBuffer(vmbuffer); bms_free(hot_attrs); + bms_free(proj_idx_attrs); bms_free(key_attrs); bms_free(id_attrs); bms_free(modified_attrs); @@ -4201,11 +4207,18 @@ l2: /* * 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 - * changed. If the page was already full, we may have skipped checking - * for index columns. If so, HOT update is possible. + * changed, or if we have projection functional indexes, check whether + * the old and the new values are the same. If the page was already + * full, we may have skipped checking for index columns. If so, HOT + * update is possible. */ - if (hot_attrs_checked && !bms_overlap(modified_attrs, hot_attrs)) + if (hot_attrs_checked + && !bms_overlap(modified_attrs, hot_attrs) + && (!bms_overlap(modified_attrs, proj_idx_attrs) + || ProjIndexIsUnchanged(relation, &oldtup, newtup))) + { use_hot_update = true; + } } else { @@ -4367,6 +4380,7 @@ l2: heap_freetuple(old_key_tuple); bms_free(hot_attrs); + bms_free(proj_idx_attrs); bms_free(key_attrs); bms_free(id_attrs); bms_free(modified_attrs); @@ -4454,6 +4468,83 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum, } /* + * Check whether the value is unchanged after update of a projection + * functional index. Compare the new and old values of the indexed + * expression to see if we are able to use a HOT update or not. + */ +static bool ProjIndexIsUnchanged(Relation relation, HeapTuple oldtup, HeapTuple newtup) +{ + ListCell *l; + List *indexoidlist = RelationGetIndexList(relation); + EState *estate = CreateExecutorState(); + ExprContext *econtext = GetPerTupleExprContext(estate); + TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(relation)); + bool equals = true; + Datum old_values[INDEX_MAX_KEYS]; + bool old_isnull[INDEX_MAX_KEYS]; + Datum new_values[INDEX_MAX_KEYS]; + bool new_isnull[INDEX_MAX_KEYS]; + int indexno = 0; + econtext->ecxt_scantuple = slot; + + foreach(l, indexoidlist) + { + if (bms_is_member(indexno, relation->rd_projidx)) + { + Oid indexOid = lfirst_oid(l); + Relation indexDesc = index_open(indexOid, AccessShareLock); + IndexInfo *indexInfo = BuildIndexInfo(indexDesc); + int i; + + ResetExprContext(econtext); + ExecStoreTuple(oldtup, slot, InvalidBuffer, false); + FormIndexDatum(indexInfo, + slot, + estate, + old_values, + old_isnull); + + ExecStoreTuple(newtup, slot, InvalidBuffer, false); + FormIndexDatum(indexInfo, + slot, + estate, + new_values, + new_isnull); + + for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) + { + if (old_isnull[i] != new_isnull[i]) + { + equals = false; + break; + } + else if (!old_isnull[i]) + { + Form_pg_attribute att = TupleDescAttr(RelationGetDescr(indexDesc), i); + if (!datumIsEqual(old_values[i], new_values[i], att->attbyval, att->attlen)) + { + equals = false; + break; + } + } + } + index_close(indexDesc, AccessShareLock); + + if (!equals) + { + break; + } + } + indexno += 1; + } + ExecDropSingleTupleTableSlot(slot); + FreeExecutorState(estate); + + return equals; +} + + +/* * Check which columns are being updated. * * Given an updated tuple, determine (and return into the output bitmapset), |