这篇文章主要介绍“PostgreSQL中ExecHashJoin依赖其他函数的实现逻辑分析”,在日常操作中,相信很多人在PostgreSQL中ExecHashJoin依赖其他函数的实现逻辑分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”PostgreSQL中ExecHashJoin依赖其他函数的实现逻辑分析”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
这些函数在HJ_NEED_NEW_OUTER阶段中使用,包括ExecHashJoinOuterGetTuple、ExecPrepHashTableForUnmatched、ExecHashGetBucketAndBatch、ExecHashGetSkewBucket、ExecHashJoinSaveTuple和ExecFetchSlotMinimalTuple等。
一、数据结构
Plan
所有计划节点通过将Plan结构作为第一个字段从Plan结构“派生”。这确保了在将节点转换为计划节点时,一切都能正常工作。(在执行器中以通用方式传递时,节点指针经常被转换为Plan *)
typedef struct Plan
{
NodeTag type;//节点类型
Cost startup_cost;
Cost total_cost;
double plan_rows;
int plan_width;
bool parallel_aware;
bool parallel_safe;
int plan_node_id;
List *targetlist;
List *qual;
struct Plan *lefttree;
struct Plan *righttree;
List *initPlan;
Bitmapset *extParam;
Bitmapset *allParam;
} Plan;
JoinState
Hash/NestLoop/Merge Join的基类
typedef struct JoinState
{
PlanState ps;//基类PlanState
JoinType jointype;//连接类型
//在找到一个匹配inner tuple的时候,如需要跳转到下一个outer tuple,则该值为T
bool single_match;
//连接条件表达式(除了ps.qual)
ExprState *joinqual;
} JoinState;
HashJoinState
Hash Join运行期状态结构体
typedef struct HashJoinTupleData *HashJoinTuple;
typedef struct HashJoinTableData *HashJoinTable;
typedef struct HashJoinState
{
JoinState js;
ExprState *hashclauses;//hash连接条件
List *hj_OuterHashKeys;
List *hj_InnerHashKeys;
List *hj_HashOperators;
HashJoinTable hj_HashTable;//Hash表
uint32 hj_CurHashValue;//当前的Hash值
int hj_CurBucketNo;//当前的bucket编号
int hj_CurSkewBucketNo;//行倾斜bucket编号
HashJoinTuple hj_CurTuple;//当前元组
TupleTableSlot *hj_OuterTupleSlot;//outer relation slot
TupleTableSlot *hj_HashTupleSlot;//Hash tuple slot
TupleTableSlot *hj_NullOuterTupleSlot;//用于外连接的outer虚拟slot
TupleTableSlot *hj_NullInnerTupleSlot;//用于外连接的inner虚拟slot
TupleTableSlot *hj_FirstOuterTupleSlot;//
int hj_JoinState;//JoinState状态
bool hj_MatchedOuter;//是否匹配
bool hj_OuterNotEmpty;//outer relation是否为空
} HashJoinState;
HashJoinTable
Hash表数据结构
typedef struct HashJoinTableData
{
int nbuckets;
int log2_nbuckets;
int nbuckets_original;
int nbuckets_optimal;
int log2_nbuckets_optimal;
//bucket [i]是内存中第i个桶中的元组链表的head item
union
{
//未共享数组是按批处理存储的,所有元组均如此
struct HashJoinTupleData **unshared;
//共享数组是每个查询的DSA区域,所有元组均如此
dsa_pointer_atomic *shared;
} buckets;
bool keepNulls;
bool skewEnabled;
HashSkewBucket **skewBucket;
int skewBucketLen;
int nSkewBuckets;
int *skewBucketNums;
int nbatch;
int curbatch;
int nbatch_original;
int nbatch_outstart;
bool growEnabled;
double totalTuples;
double partialTuples;
double skewTuples;
BufFile **innerBatchFile;
BufFile **outerBatchFile;
FmgrInfo *outer_hashfunctions;
FmgrInfo *inner_hashfunctions;
bool *hashStrict;
Size spaceUsed;
Size spaceAllowed;
Size spacePeak;
Size spaceUsedSkew;
Size spaceAllowedSkew;
MemoryContext hashCxt;
MemoryContext batchCxt;
//用于密集分配元组(到链接块中)
HashMemoryChunk chunks;
//并行hash使用的共享和私有状态
HashMemoryChunk current_chunk;
dsa_area *area;
ParallelHashJoinState *parallel_state;//并行执行状态
ParallelHashJoinBatchAccessor *batches;//并行访问器
dsa_pointer current_chunk_shared;//当前chunk的开始指针
} HashJoinTableData;
typedef struct HashJoinTableData *HashJoinTable;
HashJoinTupleData
Hash连接元组数据
typedef struct HashJoinTupleData
{
//link同一个桶中的下一个元组
union
{
struct HashJoinTupleData *unshared;
dsa_pointer shared;
} next;
uint32 hashvalue;
} HashJoinTupleData;
#define HJTUPLE_OVERHEAD MAXALIGN(sizeof(HashJoinTupleData))
#define HJTUPLE_MINTUPLE(hjtup) \
((MinimalTuple) ((char *) (hjtup) + HJTUPLE_OVERHEAD))
二、源码解读
ExecHashJoinOuterGetTuple
获取非并行模式下hashjoin的下一个外部元组:要么在第一次执行外部plan节点,要么从hashjoin批处理的临时文件中获取。
static TupleTableSlot *
ExecHashJoinOuterGetTuple(PlanState *outerNode,//outer 节点
HashJoinState *hjstate,//Hash Join执行状态
uint32 *hashvalue)//Hash值
{
HashJoinTable hashtable = hjstate->hj_HashTable;//hash表
int curbatch = hashtable->curbatch;//当前批次
TupleTableSlot *slot;//返回的slot
if (curbatch == 0)
{
slot = hjstate->hj_FirstOuterTupleSlot;
if (!TupIsNull(slot))
hjstate->hj_FirstOuterTupleSlot = NULL;//重置slot
else
slot = ExecProcNode(outerNode);//如为NULL,则获取slot
while (!TupIsNull(slot))//slot不为NULL
{
ExprContext *econtext = hjstate->js.ps.ps_ExprContext;//表达式计算上下文
econtext->ecxt_outertuple = slot;//存储获取的slot
if (ExecHashGetHashValue(hashtable, econtext,
hjstate->hj_OuterHashKeys,
true,
HJ_FILL_OUTER(hjstate),
hashvalue))//计算Hash值
{
hjstate->hj_OuterNotEmpty = true;//设置标记(outer不为空)
return slot;//返回匹配的slot
}
slot = ExecProcNode(outerNode);//继续获取下一个
}
}
else if (curbatch < hashtable->nbatch)//不是第一个批次
{
BufFile *file = hashtable->outerBatchFile[curbatch];//获取缓冲的文件
if (file == NULL)
return NULL;//如文件为NULL,则返回
slot = ExecHashJoinGetSavedTuple(hjstate,
file,
hashvalue,
hjstate->hj_OuterTupleSlot);//从文件中获取slot
if (!TupIsNull(slot))
return slot;//非NULL,则返回
}
//已完成,则返回NULL
return NULL;
}
bool
ExecHashGetHashValue(HashJoinTable hashtable,//Hash表
ExprContext *econtext,//上下文
List *hashkeys,//Hash键值链表
bool outer_tuple,//是否外表元组
bool keep_nulls,//是否保存NULL
uint32 *hashvalue)//返回的Hash值
{
uint32 hashkey = 0;//hash键
FmgrInfo *hashfunctions;//hash函数
ListCell *hk;//临时变量
int i = 0;
MemoryContext oldContext;
ResetExprContext(econtext);
//切换上下文
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
if (outer_tuple)
hashfunctions = hashtable->outer_hashfunctions;//外表元组
else
hashfunctions = hashtable->inner_hashfunctions;//内表元组
foreach(hk, hashkeys)//遍历Hash键值
{
ExprState *keyexpr = (ExprState *) lfirst(hk);//键值表达式
Datum keyval;
bool isNull;
//哈希键左移1位
hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
keyval = ExecEvalExpr(keyexpr, econtext, &isNull);
if (isNull)
{
//NULL值
if (hashtable->hashStrict[i] && !keep_nulls)
{
MemoryContextSwitchTo(oldContext);
//不保持NULL值,不匹配
return false;
}
//否则的话,不修改hashkey,仍为0
}
else
{
//不为NULL
//计算hash值
uint32 hkey;
hkey = DatumGetUInt32(FunctionCall1(&hashfunctions[i], keyval));
hashkey ^= hkey;
}
i++;//下一个键
}
//切换上下文
MemoryContextSwitchTo(oldContext);
//返回Hash键值
*hashvalue = hashkey;
return true;//成功获取
}
ExecPrepHashTableForUnmatched
为ExecScanHashTableForUnmatched函数调用作准备
void
ExecPrepHashTableForUnmatched(HashJoinState *hjstate)
{
hjstate->hj_CurBucketNo = 0;
hjstate->hj_CurSkewBucketNo = 0;
hjstate->hj_CurTuple = NULL;
}
ExecHashGetBucketAndBatch
确定哈希值的bucket号和批处理号
void
ExecHashGetBucketAndBatch(HashJoinTable hashtable,
uint32 hashvalue,
int *bucketno,
int *batchno)
{
uint32 nbuckets = (uint32) hashtable->nbuckets;//桶数
uint32 nbatch = (uint32) hashtable->nbatch;//批次号
if (nbatch > 1)//批次>1
{
//我们可以通过屏蔽来实现MOD,通过移动来实现DIV
*bucketno = hashvalue & (nbuckets - 1);//nbuckets - 1后相当于N个1
*batchno = (hashvalue >> hashtable->log2_nbuckets) & (nbatch - 1);
}
else
{
*bucketno = hashvalue & (nbuckets - 1);//只有一个批次,简单处理即可
*batchno = 0;
}
}
ExecHashGetSkewBucket
返回这个哈希值的倾斜桶的索引,如果哈希值与任何活动的倾斜桶没有关联,则返回INVALID_SKEW_BUCKET_NO。
int
ExecHashGetSkewBucket(HashJoinTable hashtable, uint32 hashvalue)
{
int bucket;
if (!hashtable->skewEnabled)
return INVALID_SKEW_BUCKET_NO;
bucket = hashvalue & (hashtable->skewBucketLen - 1);
while (hashtable->skewBucket[bucket] != NULL &&
hashtable->skewBucket[bucket]->hashvalue != hashvalue)
bucket = (bucket + 1) & (hashtable->skewBucketLen - 1);
if (hashtable->skewBucket[bucket] != NULL)
return bucket;
//否则返回INVALID_SKEW_BUCKET_NO
return INVALID_SKEW_BUCKET_NO;
}
ExecHashJoinSaveTuple
在批处理文件中保存元组.每个元组在文件中记录的是它的散列值,然后是最小化格式的元组。
void
ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue,
BufFile **fileptr)
{
BufFile *file = *fileptr;//文件指针
size_t written;//写入大小
if (file == NULL)
{
//文件指针为NULL,首次写入,则打开批处理文件
file = BufFileCreateTemp(false);
*fileptr = file;
}
//首先写入hash值,返回写入的大小
written = BufFileWrite(file, (void *) &hashvalue, sizeof(uint32));
if (written != sizeof(uint32))//写入有误,报错
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write to hash-join temporary file: %m")));
//写入tuple
written = BufFileWrite(file, (void *) tuple, tuple->t_len);
if (written != tuple->t_len)//写入有误,报错
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write to hash-join temporary file: %m")));
}
ExecFetchSlotMinimalTuple
以最小化物理元组的格式提取slot的数据
MinimalTuple
ExecFetchSlotMinimalTuple(TupleTableSlot *slot,
bool *shouldFree)
{
Assert(slot != NULL);
Assert(!TTS_EMPTY(slot));
if (slot->tts_ops->get_minimal_tuple)//调用slot->tts_ops->get_minimal_tuple
{
//调用成功,则该元组为只读,由slot负责释放
if (shouldFree)
*shouldFree = false;
return slot->tts_ops->get_minimal_tuple(slot);
}
else
{
//调用不成功,设置为true,由调用方释放
if (shouldFree)
*shouldFree = true;
return slot->tts_ops->copy_minimal_tuple(slot);//调用copy_minimal_tuple函数
}
}
三、跟踪分析
测试脚本如下
testdb=# set enable_nestloop=false;
SET
testdb=# set enable_mergejoin=false;
SET
testdb=# explain verbose select dw.*,grjf.grbh,grjf.xm,grjf.ny,grjf.je
testdb-# from t_dwxx dw,lateral (select gr.grbh,gr.xm,jf.ny,jf.je
testdb(# from t_grxx gr inner join t_jfxx jf
testdb(# on gr.dwbh = dw.dwbh
testdb(# and gr.grbh = jf.grbh) grjf
testdb-# order by dw.dwbh;
QUERY PLAN
-----------------------------------------------------------------------------------------------
Sort (cost=14828.83..15078.46 rows=99850 width=47)
Output: dw.dwmc, dw.dwbh, dw.dwdz, gr.grbh, gr.xm, jf.ny, jf.je
Sort Key: dw.dwbh
-> Hash Join (cost=3176.00..6537.55 rows=99850 width=47)
Output: dw.dwmc, dw.dwbh, dw.dwdz, gr.grbh, gr.xm, jf.ny, jf.je
Hash Cond: ((gr.grbh)::text = (jf.grbh)::text)
-> Hash Join (cost=289.00..2277.61 rows=99850 width=32)
Output: dw.dwmc, dw.dwbh, dw.dwdz, gr.grbh, gr.xm
Inner Unique: true
Hash Cond: ((gr.dwbh)::text = (dw.dwbh)::text)
-> Seq Scan on public.t_grxx gr (cost=0.00..1726.00 rows=100000 width=16)
Output: gr.dwbh, gr.grbh, gr.xm, gr.xb, gr.nl
-> Hash (cost=164.00..164.00 rows=10000 width=20)
Output: dw.dwmc, dw.dwbh, dw.dwdz
-> Seq Scan on public.t_dwxx dw (cost=0.00..164.00 rows=10000 width=20)
Output: dw.dwmc, dw.dwbh, dw.dwdz
-> Hash (cost=1637.00..1637.00 rows=100000 width=20)
Output: jf.ny, jf.je, jf.grbh
-> Seq Scan on public.t_jfxx jf (cost=0.00..1637.00 rows=100000 width=20)
Output: jf.ny, jf.je, jf.grbh
(20 rows)
启动gdb,设置断点
(gdb) b ExecHashJoinOuterGetTuple
Breakpoint 1 at 0x702edc: file nodeHashjoin.c, line 807.
(gdb) b ExecHashGetHashValue
Breakpoint 2 at 0x6ff060: file nodeHash.c, line 1778.
(gdb) b ExecHashGetBucketAndBatch
Breakpoint 3 at 0x6ff1df: file nodeHash.c, line 1880.
(gdb) b ExecHashJoinSaveTuple
Breakpoint 4 at 0x703973: file nodeHashjoin.c, line 1214.
(gdb)
ExecHashGetHashValue
ExecHashGetHashValue->进入函数ExecHashGetHashValue
(gdb) c
Continuing.
Breakpoint 2, ExecHashGetHashValue (hashtable=0x14acde8, econtext=0x149c3d0, hashkeys=0x14a8e40, outer_tuple=false,
keep_nulls=false, hashvalue=0x7ffc7eba5c20) at nodeHash.c:1778
1778 uint32 hashkey = 0;
ExecHashGetHashValue->初始化,切换内存上下文
1778 uint32 hashkey = 0;
(gdb) n
1781 int i = 0;
(gdb)
1788 ResetExprContext(econtext);
(gdb)
1790 oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
(gdb)
1792 if (outer_tuple)
ExecHashGetHashValue->inner hash函数
1792 if (outer_tuple)
(gdb)
1795 hashfunctions = hashtable->inner_hashfunctions;
ExecHashGetHashValue->获取hahs键信息
1号RTE(varnoold = 1,即t_dwxx)的dwbh字段(varattno = 2)
(gdb)
1797 foreach(hk, hashkeys)
(gdb)
1799 ExprState *keyexpr = (ExprState *) lfirst(hk);
(gdb)
1804 hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
(gdb) p *keyexpr
$1 = {tag = {type = T_ExprState}, flags = 2 '\002', resnull = false, resvalue = 0, resultslot = 0x0, steps = 0x14a8a00,
evalfunc = 0x6d1a6e <ExecInterpExprStillValid>, expr = 0x1498fc0, evalfunc_private = 0x6d1e97 <ExecJustInnerVar>,
steps_len = 3, steps_alloc = 16, parent = 0x149b738, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0,
innermost_domainval = 0x0, innermost_domainnull = 0x0}
(gdb) p *(RelabelType *)keyexpr->expr
$3 = {xpr = {type = T_RelabelType}, arg = 0x1499018, resulttype = 25, resulttypmod = -1, resultcollid = 100,
relabelformat = COERCE_IMPLICIT_CAST, location = -1}
(gdb) p *((RelabelType *)keyexpr->expr)->arg
$4 = {type = T_Var}
(gdb) p *(Var *)((RelabelType *)keyexpr->expr)->arg
$5 = {xpr = {type = T_Var}, varno = 65000, varattno = 2, vartype = 1043, vartypmod = 24, varcollid = 100, varlevelsup = 0,
varnoold = 1, varoattno = 2, location = 218}
(gdb)
ExecHashGetHashValue->获取hash值,解析表达式
(gdb) n
1809 keyval = ExecEvalExpr(keyexpr, econtext, &isNull);
(gdb)
1824 if (isNull)
(gdb) p hashkey
$6 = 0
(gdb) p keyval
$7 = 140460362257270
(gdb)
ExecHashGetHashValue->返回值不为NULL
(gdb) p isNull
$8 = false
(gdb) n
1838 hkey = DatumGetUInt32(FunctionCall1(&hashfunctions[i], keyval));
ExecHashGetHashValue->计算Hash值
(gdb) n
1839 hashkey ^= hkey;
(gdb) p hkey
$9 = 3663833849
(gdb) p hashkey
$10 = 0
(gdb) n
1842 i++;
(gdb) p hashkey
$11 = 3663833849
(gdb)
ExecHashGetHashValue->返回计算结果
(gdb) n
1797 foreach(hk, hashkeys)
(gdb)
1845 MemoryContextSwitchTo(oldContext);
(gdb)
1847 *hashvalue = hashkey;
(gdb)
1848 return true;
(gdb)
1849 }
ExecHashGetBucketAndBatch
ExecHashGetBucketAndBatch->进入ExecHashGetBucketAndBatch
(gdb) c
Continuing.
Breakpoint 3, ExecHashGetBucketAndBatch (hashtable=0x14acde8, hashvalue=3663833849, bucketno=0x7ffc7eba5bdc,
batchno=0x7ffc7eba5bd8) at nodeHash.c:1880
1880 uint32 nbuckets = (uint32) hashtable->nbuckets;
ExecHashGetBucketAndBatch->获取bucket数和批次数
1880 uint32 nbuckets = (uint32) hashtable->nbuckets;
(gdb) n
1881 uint32 nbatch = (uint32) hashtable->nbatch;
(gdb)
1883 if (nbatch > 1)
(gdb) p nbuckets
$12 = 16384
(gdb) p nbatch
$13 = 1
(gdb)
ExecHashGetBucketAndBatch->计算桶号和批次号(只有一个批次,设置为0)
(gdb) n
1891 *bucketno = hashvalue & (nbuckets - 1);
(gdb)
1892 *batchno = 0;
(gdb)
1894 }
(gdb) p bucketno
$14 = (int *) 0x7ffc7eba5bdc
(gdb) p *bucketno
$15 = 11001
(gdb)
ExecHashJoinOuterGetTuple
ExecHashJoinOuterGetTuple->进入ExecHashJoinOuterGetTuple函数
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000702edc in ExecHashJoinOuterGetTuple at nodeHashjoin.c:807
2 breakpoint keep y 0x00000000006ff060 in ExecHashGetHashValue at nodeHash.c:1778
breakpoint already hit 4 times
3 breakpoint keep y 0x00000000006ff1df in ExecHashGetBucketAndBatch at nodeHash.c:1880
breakpoint already hit 4 times
4 breakpoint keep y 0x0000000000703973 in ExecHashJoinSaveTuple at nodeHashjoin.c:1214
(gdb) del 2
(gdb) del 3
(gdb) c
Continuing.
Breakpoint 1, ExecHashJoinOuterGetTuple (outerNode=0x149ba10, hjstate=0x149b738, hashvalue=0x7ffc7eba5ccc)
at nodeHashjoin.c:807
807 HashJoinTable hashtable = hjstate->hj_HashTable;
(gdb)
ExecHashJoinOuterGetTuple->查看输入参数
outerNode:outer relation为顺序扫描得到的relation(对t_jfxx进行顺序扫描)
hjstate:Hash Join执行状态
hashvalue:Hash值
(gdb) p *outerNode
$16 = {type = T_SeqScanState, plan = 0x1494d10, state = 0x149b0f8, ExecProcNode = 0x71578d <ExecSeqScan>,
ExecProcNodeReal = 0x71578d <ExecSeqScan>, instrument = 0x0, worker_instrument = 0x0, worker_jit_instrument = 0x0,
qual = 0x0, lefttree = 0x0, righttree = 0x0, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0,
ps_ResultTupleSlot = 0x149c178, ps_ExprContext = 0x149bb28, ps_ProjInfo = 0x0, scandesc = 0x7fbfa69a8308}
(gdb) p *hjstate
$17 = {js = {ps = {type = T_HashJoinState, plan = 0x1496d18, state = 0x149b0f8, ExecProcNode = 0x70291d <ExecHashJoin>,
ExecProcNodeReal = 0x70291d <ExecHashJoin>, instrument = 0x0, worker_instrument = 0x0, worker_jit_instrument = 0x0,
qual = 0x0, lefttree = 0x149ba10, righttree = 0x149c2b8, initPlan = 0x0, subPlan = 0x0, chgParam = 0x0,
ps_ResultTupleSlot = 0x14a7498, ps_ExprContext = 0x149b950, ps_ProjInfo = 0x149cef0, scandesc = 0x0},
jointype = JOIN_INNER, single_match = true, joinqual = 0x0}, hashclauses = 0x14a7b30, hj_OuterHashKeys = 0x14a8930,
hj_InnerHashKeys = 0x14a8e40, hj_HashOperators = 0x14a8ea0, hj_HashTable = 0x14acde8, hj_CurHashValue = 0,
hj_CurBucketNo = 0, hj_CurSkewBucketNo = -1, hj_CurTuple = 0x0, hj_OuterTupleSlot = 0x14a79f0,
hj_HashTupleSlot = 0x149cc18, hj_NullOuterTupleSlot = 0x0, hj_NullInnerTupleSlot = 0x0,
hj_FirstOuterTupleSlot = 0x149bbe8, hj_JoinState = 2, hj_MatchedOuter = false, hj_OuterNotEmpty = false}
(gdb) p *hashvalue
$18 = 32703
(gdb)
ExecHashJoinOuterGetTuple->只有一个批次,批次号为0
(gdb) n
808 int curbatch = hashtable->curbatch;
(gdb)
811 if (curbatch == 0)
(gdb) p curbatch
$20 = 0
ExecHashJoinOuterGetTuple->获取首个outer tuple slot(不为NULL),重置hjstate->hj_FirstOuterTupleSlot为NULL
(gdb) n
817 slot = hjstate->hj_FirstOuterTupleSlot;
(gdb)
818 if (!TupIsNull(slot))
(gdb) p *slot
$21 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = false,
tts_tuple = 0x14ac200, tts_tupleDescriptor = 0x7fbfa69a8308, tts_mcxt = 0x149afe0, tts_buffer = 345, tts_nvalid = 0,
tts_values = 0x149bc48, tts_isnull = 0x149bc70, tts_mintuple = 0x0, tts_minhdr = {t_len = 0, t_self = {ip_blkid = {
bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x0}, tts_off = 0, tts_fixedTupleDescriptor = true}
(gdb)
(gdb) n
819 hjstate->hj_FirstOuterTupleSlot = NULL;
(gdb)
ExecHashJoinOuterGetTuple->循环获取,找到匹配的slot
(gdb)
823 while (!TupIsNull(slot))
(gdb) n
828 ExprContext *econtext = hjstate->js.ps.ps_ExprContext;
(gdb)
ExecHashJoinOuterGetTuple->成功匹配,返回slot
(gdb) n
830 econtext->ecxt_outertuple = slot;
(gdb)
834 HJ_FILL_OUTER(hjstate),
(gdb)
831 if (ExecHashGetHashValue(hashtable, econtext,
(gdb)
838 hjstate->hj_OuterNotEmpty = true;
(gdb)
840 return slot;
(gdb) p *slot
$22 = {type = T_TupleTableSlot, tts_isempty = false, tts_shouldFree = false, tts_shouldFreeMin = false, tts_slow = true,
tts_tuple = 0x14ac200, tts_tupleDescriptor = 0x7fbfa69a8308, tts_mcxt = 0x149afe0, tts_buffer = 345, tts_nvalid = 1,
tts_values = 0x149bc48, tts_isnull = 0x149bc70, tts_mintuple = 0x0, tts_minhdr = {t_len = 0, t_self = {ip_blkid = {
bi_hi = 0, bi_lo = 0}, ip_posid = 0}, t_tableOid = 0, t_data = 0x0}, tts_off = 2, tts_fixedTupleDescriptor = true}
(gdb)
到此,关于“PostgreSQL中ExecHashJoin依赖其他函数的实现逻辑分析”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!