mirror of
https://github.com/postgres/postgres.git
synced 2026-10-29 15:58:33 +03:00
Emit null-keyed hash join outer tuples immediately when feasible.
Commit 1811f1af9 put outer tuples with null join keys into a
tuplestore and emitted them at the end of the batch, which risks
expensive spills to disk if there are a lot of them. It's difficult
to avoid the tuplestore in a multi-batch parallel join. But in the
non-parallel and single-batch cases we can just emit such tuples
immediately, at the tiny cost of propagating the "is-null" result
flag one call level further up. This avoids a performance regression
from v18 for such cases.
Author: shihao zhong <zhong950419@gmail.com>
Co-authored-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/CAGRkXqTgugmYrtnP_11NjL_GCXwAfzVfm5WKUNFsfZOzxDF2rA@mail.gmail.com
Backpatch-through: 19
This commit is contained in:
@@ -195,10 +195,12 @@
|
||||
|
||||
static TupleTableSlot *ExecHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
HashJoinState *hjstate,
|
||||
uint32 *hashvalue);
|
||||
uint32 *hashvalue,
|
||||
bool *is_null);
|
||||
static TupleTableSlot *ExecParallelHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
HashJoinState *hjstate,
|
||||
uint32 *hashvalue);
|
||||
uint32 *hashvalue,
|
||||
bool *is_null);
|
||||
static TupleTableSlot *ExecHashJoinGetSavedTuple(HashJoinState *hjstate,
|
||||
BufFile *file,
|
||||
uint32 *hashvalue,
|
||||
@@ -233,6 +235,7 @@ ExecHashJoinImpl(PlanState *pstate, bool parallel)
|
||||
HashJoinTable hashtable;
|
||||
TupleTableSlot *outerTupleSlot;
|
||||
uint32 hashvalue;
|
||||
bool is_null;
|
||||
int batchno;
|
||||
ParallelHashJoinState *parallel_state;
|
||||
|
||||
@@ -440,10 +443,13 @@ ExecHashJoinImpl(PlanState *pstate, bool parallel)
|
||||
if (parallel)
|
||||
outerTupleSlot =
|
||||
ExecParallelHashJoinOuterGetTuple(outerNode, node,
|
||||
&hashvalue);
|
||||
&hashvalue,
|
||||
&is_null);
|
||||
else
|
||||
outerTupleSlot =
|
||||
ExecHashJoinOuterGetTuple(outerNode, node, &hashvalue);
|
||||
ExecHashJoinOuterGetTuple(outerNode, node,
|
||||
&hashvalue,
|
||||
&is_null);
|
||||
|
||||
if (TupIsNull(outerTupleSlot))
|
||||
{
|
||||
@@ -489,6 +495,16 @@ ExecHashJoinImpl(PlanState *pstate, bool parallel)
|
||||
econtext->ecxt_outertuple = outerTupleSlot;
|
||||
node->hj_MatchedOuter = false;
|
||||
|
||||
/*
|
||||
* If the tuple can't match anything, skip all the tedium and
|
||||
* go directly to emitting it in null-extended form.
|
||||
*/
|
||||
if (is_null)
|
||||
{
|
||||
node->hj_JoinState = HJ_FILL_OUTER_TUPLE;
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the corresponding bucket for this tuple in the main
|
||||
* hash table or skew hash table.
|
||||
@@ -1104,12 +1120,15 @@ ExecEndHashJoin(HashJoinState *node)
|
||||
* Returns a null slot if no more outer tuples (within the current batch).
|
||||
*
|
||||
* On success, the tuple's hash value is stored at *hashvalue --- this is
|
||||
* either originally computed, or re-read from the temp file.
|
||||
* either originally computed, or re-read from the temp file. Also, we
|
||||
* set *is_null to true if the tuple cannot have any matches due to
|
||||
* containing a null join key (in which case the hashvalue is meaningless).
|
||||
*/
|
||||
static TupleTableSlot *
|
||||
ExecHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
HashJoinState *hjstate,
|
||||
uint32 *hashvalue)
|
||||
uint32 *hashvalue,
|
||||
bool *is_null)
|
||||
{
|
||||
HashJoinTable hashtable = hjstate->hj_HashTable;
|
||||
int curbatch = hashtable->curbatch;
|
||||
@@ -1129,8 +1148,6 @@ ExecHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
|
||||
while (!TupIsNull(slot))
|
||||
{
|
||||
bool isnull;
|
||||
|
||||
/*
|
||||
* We have to compute the tuple's hash value.
|
||||
*/
|
||||
@@ -1142,9 +1159,9 @@ ExecHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
|
||||
*hashvalue = DatumGetUInt32(ExecEvalExprSwitchContext(hjstate->hj_OuterHash,
|
||||
econtext,
|
||||
&isnull));
|
||||
is_null));
|
||||
|
||||
if (!isnull)
|
||||
if (!*is_null)
|
||||
{
|
||||
/* normal case with a non-null join key */
|
||||
/* remember outer relation is not empty for possible rescan */
|
||||
@@ -1154,10 +1171,8 @@ ExecHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
}
|
||||
else if (hjstate->hj_KeepNullTuples)
|
||||
{
|
||||
/* null join key, but we must save tuple to be emitted later */
|
||||
if (hjstate->hj_NullOuterTupleStore == NULL)
|
||||
hjstate->hj_NullOuterTupleStore = ExecHashBuildNullTupleStore(hashtable);
|
||||
tuplestore_puttupleslot(hjstate->hj_NullOuterTupleStore, slot);
|
||||
/* null join key, but we must return tuple to be emitted */
|
||||
return slot;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1183,7 +1198,10 @@ ExecHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
hashvalue,
|
||||
hjstate->hj_OuterTupleSlot);
|
||||
if (!TupIsNull(slot))
|
||||
{
|
||||
*is_null = false; /* else it'd not be in the batch file */
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
|
||||
/* End of this batch */
|
||||
@@ -1196,7 +1214,8 @@ ExecHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
static TupleTableSlot *
|
||||
ExecParallelHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
HashJoinState *hjstate,
|
||||
uint32 *hashvalue)
|
||||
uint32 *hashvalue,
|
||||
bool *is_null)
|
||||
{
|
||||
HashJoinTable hashtable = hjstate->hj_HashTable;
|
||||
int curbatch = hashtable->curbatch;
|
||||
@@ -1213,8 +1232,6 @@ ExecParallelHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
|
||||
while (!TupIsNull(slot))
|
||||
{
|
||||
bool isnull;
|
||||
|
||||
ExprContext *econtext = hjstate->js.ps.ps_ExprContext;
|
||||
|
||||
econtext->ecxt_outertuple = slot;
|
||||
@@ -1223,19 +1240,17 @@ ExecParallelHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
|
||||
*hashvalue = DatumGetUInt32(ExecEvalExprSwitchContext(hjstate->hj_OuterHash,
|
||||
econtext,
|
||||
&isnull));
|
||||
is_null));
|
||||
|
||||
if (!isnull)
|
||||
if (!*is_null)
|
||||
{
|
||||
/* normal case with a non-null join key */
|
||||
return slot;
|
||||
}
|
||||
else if (hjstate->hj_KeepNullTuples)
|
||||
{
|
||||
/* null join key, but we must save tuple to be emitted later */
|
||||
if (hjstate->hj_NullOuterTupleStore == NULL)
|
||||
hjstate->hj_NullOuterTupleStore = ExecHashBuildNullTupleStore(hashtable);
|
||||
tuplestore_puttupleslot(hjstate->hj_NullOuterTupleStore, slot);
|
||||
/* null join key, but we must return tuple to be emitted */
|
||||
return slot;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1256,6 +1271,7 @@ ExecParallelHashJoinOuterGetTuple(PlanState *outerNode,
|
||||
ExecForceStoreMinimalTuple(tuple,
|
||||
hjstate->hj_OuterTupleSlot,
|
||||
false);
|
||||
*is_null = false; /* else it'd not be in the batch file */
|
||||
slot = hjstate->hj_OuterTupleSlot;
|
||||
return slot;
|
||||
}
|
||||
|
||||
@@ -74,11 +74,12 @@
|
||||
* If an input tuple has a null join key, then it cannot match anything from
|
||||
* the other side of the join. Normally we can just discard such a tuple
|
||||
* immediately, but if it comes from the outer side of an outer join then we
|
||||
* must emit it with null-extension of the other side. For various reasons
|
||||
* it's not convenient to do that immediately on seeing the tuple, so we dump
|
||||
* the tuple into a tuplestore and emit it later. (In the unlikely but
|
||||
* supported case of a non-strict join operator, we treat null keys as normal
|
||||
* data.)
|
||||
* must emit it with null-extension of the other side. For an outer tuple
|
||||
* it's possible to emit it immediately and then discard it. But for an inner
|
||||
* tuple, or an outer tuple of a multi-batch parallel join, that is not
|
||||
* convenient, so we dump the tuple into a tuplestore and emit it later.
|
||||
* (In the unlikely but supported case of a non-strict join operator, we treat
|
||||
* null keys as normal data.)
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
@@ -2203,7 +2203,8 @@ typedef struct MergeJoinState
|
||||
* outer joins
|
||||
* hj_NullInnerTupleSlot prepared null tuple for left/full outer joins
|
||||
* hj_NullOuterTupleStore tuplestore holding outer tuples that have
|
||||
* null join keys (but must be emitted anyway)
|
||||
* null join keys (but must be emitted anyway);
|
||||
* used only in multi-batch parallel joins
|
||||
* hj_FirstOuterTupleSlot first tuple retrieved from outer plan
|
||||
* hj_JoinState current state of ExecHashJoin state machine
|
||||
* hj_KeepNullTuples true to keep outer tuples with null join keys
|
||||
|
||||
Reference in New Issue
Block a user