PostgreSQL的ExecHashJoin依赖其他函数的实现逻辑是什么
发表于:2025-01-21 作者:千家信息网编辑
千家信息网最后更新 2025年01月21日,本篇内容介绍了"PostgreSQL的ExecHashJoin依赖其他函数的实现逻辑是什么"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况
千家信息网最后更新 2025年01月21日PostgreSQL的ExecHashJoin依赖其他函数的实现逻辑是什么
本篇内容介绍了"PostgreSQL的ExecHashJoin依赖其他函数的实现逻辑是什么"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
一、数据结构
JoinState
Hash/NestLoop/Merge Join的基类
/* ---------------- * JoinState information * * Superclass for state nodes of join plans. * Hash/NestLoop/Merge Join的基类 * ---------------- */typedef struct JoinState{ PlanState ps;//基类PlanState JoinType jointype;//连接类型 //在找到一个匹配inner tuple的时候,如需要跳转到下一个outer tuple,则该值为T bool single_match; /* True if we should skip to next outer tuple * after finding one inner match */ //连接条件表达式(除了ps.qual) ExprState *joinqual; /* JOIN quals (in addition to ps.qual) */} JoinState;
HashJoinState
Hash Join运行期状态结构体
/* these structs are defined in executor/hashjoin.h: */typedef struct HashJoinTupleData *HashJoinTuple;typedef struct HashJoinTableData *HashJoinTable;typedef struct HashJoinState{ JoinState js; /* 基类;its first field is NodeTag */ ExprState *hashclauses;//hash连接条件 List *hj_OuterHashKeys; /* 外表条件链表;list of ExprState nodes */ List *hj_InnerHashKeys; /* 内表连接条件;list of ExprState nodes */ List *hj_HashOperators; /* 操作符OIDs链表;list of operator OIDs */ 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; /* 内存中的hash桶数;# buckets in the in-memory hash table */ int log2_nbuckets; /* 2的对数(nbuckets必须是2的幂);its log2 (nbuckets must be a power of 2) */ int nbuckets_original; /* 首次hash时的桶数;# buckets when starting the first hash */ int nbuckets_optimal; /* 优化后的桶数(每个批次);optimal # buckets (per batch) */ int log2_nbuckets_optimal; /* 2的对数;log2(nbuckets_optimal) */ /* buckets[i] is head of list of tuples in i'th in-memory bucket */ //bucket [i]是内存中第i个桶中的元组链表的head item union { /* unshared array is per-batch storage, as are all the tuples */ //未共享数组是按批处理存储的,所有元组均如此 struct HashJoinTupleData **unshared; /* shared array is per-query DSA area, as are all the tuples */ //共享数组是每个查询的DSA区域,所有元组均如此 dsa_pointer_atomic *shared; } buckets; bool keepNulls; /*如不匹配则存储NULL元组,该值为T;true to store unmatchable NULL tuples */ bool skewEnabled; /*是否使用倾斜优化?;are we using skew optimization? */ HashSkewBucket **skewBucket; /* 倾斜的hash表桶数;hashtable of skew buckets */ int skewBucketLen; /* skewBucket数组大小;size of skewBucket array (a power of 2!) */ int nSkewBuckets; /* 活动的倾斜桶数;number of active skew buckets */ int *skewBucketNums; /* 活动倾斜桶数组索引;array indexes of active skew buckets */ int nbatch; /* 批次数;number of batches */ int curbatch; /* 当前批次,第一轮为0;current batch #; 0 during 1st pass */ int nbatch_original; /* 在开始inner扫描时的批次;nbatch when we started inner scan */ int nbatch_outstart; /* 在开始outer扫描时的批次;nbatch when we started outer scan */ bool growEnabled; /* 关闭nbatch增加的标记;flag to shut off nbatch increases */ double totalTuples; /* 从inner plan获得的元组数;# tuples obtained from inner plan */ double partialTuples; /* 通过hashjoin获得的inner元组数;# tuples obtained from inner plan by me */ double skewTuples; /* 倾斜元组数;# tuples inserted into skew tuples */ /* * These arrays are allocated for the life of the hash join, but only if * nbatch > 1. A file is opened only when we first write a tuple into it * (otherwise its pointer remains NULL). Note that the zero'th array * elements never get used, since we will process rather than dump out any * tuples of batch zero. * 这些数组在散列连接的生命周期内分配,但仅当nbatch > 1时分配。 * 只有当第一次将元组写入文件时,文件才会打开(否则它的指针将保持NULL)。 * 注意,第0个数组元素永远不会被使用,因为批次0的元组永远不会转储. */ BufFile **innerBatchFile; /* 每个批次的inner虚拟临时文件缓存;buffered virtual temp file per batch */ BufFile **outerBatchFile; /* 每个批次的outer虚拟临时文件缓存;buffered virtual temp file per batch */ /* * Info about the datatype-specific hash functions for the datatypes being * hashed. These are arrays of the same length as the number of hash join * clauses (hash keys). * 有关正在散列的数据类型的特定于数据类型的散列函数的信息。 * 这些数组的长度与散列连接子句(散列键)的数量相同。 */ FmgrInfo *outer_hashfunctions; /* outer hash函数FmgrInfo结构体;lookup data for hash functions */ FmgrInfo *inner_hashfunctions; /* inner hash函数FmgrInfo结构体;lookup data for hash functions */ bool *hashStrict; /* 每个hash操作符是严格?is each hash join operator strict? */ Size spaceUsed; /* 元组使用的当前内存空间大小;memory space currently used by tuples */ Size spaceAllowed; /* 空间使用上限;upper limit for space used */ Size spacePeak; /* 峰值的空间使用;peak space used */ Size spaceUsedSkew; /* 倾斜哈希表的当前空间使用情况;skew hash table's current space usage */ Size spaceAllowedSkew; /* 倾斜哈希表的使用上限;upper limit for skew hashtable */ MemoryContext hashCxt; /* 整个散列连接存储的上下文;context for whole-hash-join storage */ MemoryContext batchCxt; /* 该批次存储的上下文;context for this-batch-only storage */ /* used for dense allocation of tuples (into linked chunks) */ //用于密集分配元组(到链接块中) HashMemoryChunk chunks; /* 整个批次使用一个链表;one list for the whole batch */ /* Shared and private state for Parallel Hash. */ //并行hash使用的共享和私有状态 HashMemoryChunk current_chunk; /* 后台进程的当前chunk;this backend's current chunk */ dsa_area *area; /* 用于分配内存的DSA区域;DSA area to allocate memory from */ ParallelHashJoinState *parallel_state;//并行执行状态 ParallelHashJoinBatchAccessor *batches;//并行访问器 dsa_pointer current_chunk_shared;//当前chunk的开始指针} HashJoinTableData;typedef struct HashJoinTableData *HashJoinTable;
HashJoinTupleData
Hash连接元组数据
/* ---------------------------------------------------------------- * hash-join hash table structures * * Each active hashjoin has a HashJoinTable control block, which is * palloc'd in the executor's per-query context. All other storage needed * for the hashjoin is kept in private memory contexts, two for each hashjoin. * This makes it easy and fast to release the storage when we don't need it * anymore. (Exception: data associated with the temp files lives in the * per-query context too, since we always call buffile.c in that context.) * 每个活动的hashjoin都有一个可散列的控制块,它在执行程序的每个查询上下文中都是通过palloc分配的。 * hashjoin所需的所有其他存储都保存在私有内存上下文中,每个hashjoin有两个。 * 当不再需要它的时候,这使得释放它变得简单和快速。 * (例外:与临时文件相关的数据也存在于每个查询上下文中,因为在这种情况下总是调用buffile.c。) * * The hashtable contexts are made children of the per-query context, ensuring * that they will be discarded at end of statement even if the join is * aborted early by an error. (Likewise, any temporary files we make will * be cleaned up by the virtual file manager in event of an error.) * hashtable上下文是每个查询上下文的子上下文,确保在语句结束时丢弃它们,即使连接因错误而提前中止。 * (同样,如果出现错误,虚拟文件管理器将清理创建的任何临时文件。) * * Storage that should live through the entire join is allocated from the * "hashCxt", while storage that is only wanted for the current batch is * allocated in the "batchCxt". By resetting the batchCxt at the end of * each batch, we free all the per-batch storage reliably and without tedium. * 通过整个连接的存储空间应从"hashCxt"分配,而只需要当前批处理的存储空间在"batchCxt"中分配。 * 通过在每个批处理结束时重置batchCxt,可以可靠地释放每个批处理的所有存储,而不会感到单调乏味。 * * During first scan of inner relation, we get its tuples from executor. * If nbatch > 1 then tuples that don't belong in first batch get saved * into inner-batch temp files. The same statements apply for the * first scan of the outer relation, except we write tuples to outer-batch * temp files. After finishing the first scan, we do the following for * each remaining batch: * 1. Read tuples from inner batch file, load into hash buckets. * 2. Read tuples from outer batch file, match to hash buckets and output. * 在内部关系的第一次扫描中,从执行者那里得到了它的元组。 * 如果nbatch > 1,那么不属于第一批的元组将保存到批内临时文件中。 * 相同的语句适用于外关系的第一次扫描,但是我们将元组写入外部批处理临时文件。 * 完成第一次扫描后,我们对每批剩余的元组做如下处理: * 1.从内部批处理文件读取元组,加载到散列桶中。 * 2.从外部批处理文件读取元组,匹配哈希桶和输出。 * * It is possible to increase nbatch on the fly if the in-memory hash table * gets too big. The hash-value-to-batch computation is arranged so that this * can only cause a tuple to go into a later batch than previously thought, * never into an earlier batch. When we increase nbatch, we rescan the hash * table and dump out any tuples that are now of a later batch to the correct * inner batch file. Subsequently, while reading either inner or outer batch * files, we might find tuples that no longer belong to the current batch; * if so, we just dump them out to the correct batch file. * 如果内存中的哈希表太大,可以动态增加nbatch。 * 散列值到批处理的计算是这样安排的: * 这只会导致元组进入比以前认为的更晚的批处理,而不会进入更早的批处理。 * 当增加nbatch时,重新扫描哈希表,并将现在属于后面批处理的任何元组转储到正确的内部批处理文件。 * 随后,在读取内部或外部批处理文件时,可能会发现不再属于当前批处理的元组; * 如果是这样,只需将它们转储到正确的批处理文件即可。 * ---------------------------------------------------------------- *//* these are in nodes/execnodes.h: *//* typedef struct HashJoinTupleData *HashJoinTuple; *//* typedef struct HashJoinTableData *HashJoinTable; */typedef struct HashJoinTupleData{ /* link to next tuple in same bucket */ //link同一个桶中的下一个元组 union { struct HashJoinTupleData *unshared; dsa_pointer shared; } next; uint32 hashvalue; /* 元组的hash值;tuple's hash code */ /* Tuple data, in MinimalTuple format, follows on a MAXALIGN boundary */} HashJoinTupleData;#define HJTUPLE_OVERHEAD MAXALIGN(sizeof(HashJoinTupleData))#define HJTUPLE_MINTUPLE(hjtup) \ ((MinimalTuple) ((char *) (hjtup) + HJTUPLE_OVERHEAD))
二、源码解读
ExecScanHashBucket
搜索匹配当前outer relation tuple的hash桶,寻找匹配的inner relation元组。
/*---------------------------------------------------------------------------------------------------- HJ_SCAN_BUCKET 阶段----------------------------------------------------------------------------------------------------*//* * ExecScanHashBucket * scan a hash bucket for matches to the current outer tuple * 搜索匹配当前outer relation tuple的hash桶 * * The current outer tuple must be stored in econtext->ecxt_outertuple. * 当前的outer relation tuple必须存储在econtext->ecxt_outertuple中 * * On success, the inner tuple is stored into hjstate->hj_CurTuple and * econtext->ecxt_innertuple, using hjstate->hj_HashTupleSlot as the slot * for the latter. * 成功后,内部元组存储到hjstate->hj_CurTuple和econtext->ecxt_innertuple中, * 使用hjstate->hj_HashTupleSlot作为后者的slot。 */boolExecScanHashBucket(HashJoinState *hjstate, ExprContext *econtext){ ExprState *hjclauses = hjstate->hashclauses;//hash连接条件表达式 HashJoinTable hashtable = hjstate->hj_HashTable;//Hash表 HashJoinTuple hashTuple = hjstate->hj_CurTuple;//当前的Tuple uint32 hashvalue = hjstate->hj_CurHashValue;//hash值 /* * hj_CurTuple is the address of the tuple last returned from the current * bucket, or NULL if it's time to start scanning a new bucket. * hj_CurTuple是最近从当前桶返回的元组的地址,如果需要开始扫描新桶,则为NULL。 * * If the tuple hashed to a skew bucket then scan the skew bucket * otherwise scan the standard hashtable bucket. * 如果元组散列到倾斜桶,则扫描倾斜桶,否则扫描标准哈希表桶。 */ if (hashTuple != NULL) hashTuple = hashTuple->next.unshared;//hashTuple,通过指针获取下一个 else if (hjstate->hj_CurSkewBucketNo != INVALID_SKEW_BUCKET_NO) //如为NULL,而且使用倾斜优化,则从倾斜桶中获取 hashTuple = hashtable->skewBucket[hjstate->hj_CurSkewBucketNo]->tuples; else ////如为NULL,不使用倾斜优化,从常规的bucket中获取 hashTuple = hashtable->buckets.unshared[hjstate->hj_CurBucketNo]; while (hashTuple != NULL)//循环 { if (hashTuple->hashvalue == hashvalue)//hash值一致 { TupleTableSlot *inntuple;//inner tuple /* insert hashtable's tuple into exec slot so ExecQual sees it */ //把Hash表中的tuple插入到执行器的slot中,作为函数ExecQual的输入使用 inntuple = ExecStoreMinimalTuple(HJTUPLE_MINTUPLE(hashTuple), hjstate->hj_HashTupleSlot, false); /* do not pfree */ econtext->ecxt_innertuple = inntuple;//赋值 if (ExecQualAndReset(hjclauses, econtext))//判断连接条件是否满足 { hjstate->hj_CurTuple = hashTuple;//满足,则赋值&返回T return true; } } hashTuple = hashTuple->next.unshared;//从Hash表中获取下一个tuple } /* * no match * 不匹配,返回F */ return false;}/* * Store a minimal tuple into TTSOpsMinimalTuple type slot. * 存储最小化的tuple到TTSOpsMinimalTuple类型的slot中 * * If the target slot is not guaranteed to be TTSOpsMinimalTuple type slot, * use the, more expensive, ExecForceStoreMinimalTuple(). * 如果目标slot不能确保是TTSOpsMinimalTuple类型,使用代价更高的ExecForceStoreMinimalTuple()函数 */TupleTableSlot *ExecStoreMinimalTuple(MinimalTuple mtup, TupleTableSlot *slot, bool shouldFree){ /* * sanity checks * 安全检查 */ Assert(mtup != NULL); Assert(slot != NULL); Assert(slot->tts_tupleDescriptor != NULL); if (unlikely(!TTS_IS_MINIMALTUPLE(slot)))//类型检查 elog(ERROR, "trying to store a minimal tuple into wrong type of slot"); tts_minimal_store_tuple(slot, mtup, shouldFree);//存储 return slot;//返回slot}static voidtts_minimal_store_tuple(TupleTableSlot *slot, MinimalTuple mtup, bool shouldFree){ MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;//获取slot tts_minimal_clear(slot);//清除原来的slot //安全检查 Assert(!TTS_SHOULDFREE(slot)); Assert(TTS_EMPTY(slot)); //设置slot信息 slot->tts_flags &= ~TTS_FLAG_EMPTY; slot->tts_nvalid = 0; mslot->off = 0; //存储到mslot中 mslot->mintuple = mtup; Assert(mslot->tuple == &mslot->minhdr); mslot->minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET; mslot->minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET); /* no need to set t_self or t_tableOid since we won't allow access */ //不需要设置t_sefl或者t_tableOid,因为不允许访问 if (shouldFree) slot->tts_flags |= TTS_FLAG_SHOULDFREE; else Assert(!TTS_SHOULDFREE(slot));} /* * ExecQualAndReset() - evaluate qual with ExecQual() and reset expression * context. * ExecQualAndReset() - 使用ExecQual()解析并重置表达式 */#ifndef FRONTENDstatic inline boolExecQualAndReset(ExprState *state, ExprContext *econtext){ bool ret = ExecQual(state, econtext);//调用ExecQual /* inline ResetExprContext, to avoid ordering issue in this file */ //内联ResetExprContext,避免在这个文件中的ordering MemoryContextReset(econtext->ecxt_per_tuple_memory); return ret;}#endif#define HeapTupleHeaderSetMatch(tup) \( \ (tup)->t_infomask2 |= HEAP_TUPLE_HAS_MATCH \)
三、跟踪分析
测试脚本如下
testdb=# set enable_nestloop=false;SETtestdb=# set enable_mergejoin=false;SETtestdb=# 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) grjftestdb-# 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 ExecScanHashBucketBreakpoint 1 at 0x6ff25b: file nodeHash.c, line 1910.(gdb) cContinuing.Breakpoint 1, ExecScanHashBucket (hjstate=0x2bb8738, econtext=0x2bb8950) at nodeHash.c:19101910 ExprState *hjclauses = hjstate->hashclauses;
设置相关变量
1910 ExprState *hjclauses = hjstate->hashclauses;(gdb) n1911 HashJoinTable hashtable = hjstate->hj_HashTable;(gdb) 1912 HashJoinTuple hashTuple = hjstate->hj_CurTuple;(gdb) 1913 uint32 hashvalue = hjstate->hj_CurHashValue;(gdb) 1922 if (hashTuple != NULL)
hash join连接条件
(gdb) p *hjclauses$1 = {tag = {type = T_ExprState}, flags = 7 '\a', resnull = false, resvalue = 0, resultslot = 0x0, steps = 0x2bc4bc8, evalfunc = 0x6d1a6e, expr = 0x2bb60c0, evalfunc_private = 0x6cf625 , steps_len = 7, steps_alloc = 16, parent = 0x2bb8738, ext_params = 0x0, innermost_caseval = 0x0, innermost_casenull = 0x0, innermost_domainval = 0x0, innermost_domainnull = 0x0}
hash表
(gdb) p hashtable$2 = (HashJoinTable) 0x2bc9de8(gdb) p *hashtable$3 = {nbuckets = 16384, log2_nbuckets = 14, nbuckets_original = 16384, nbuckets_optimal = 16384, log2_nbuckets_optimal = 14, buckets = {unshared = 0x7f0fc1345050, shared = 0x7f0fc1345050}, keepNulls = false, skewEnabled = false, skewBucket = 0x0, skewBucketLen = 0, nSkewBuckets = 0, skewBucketNums = 0x0, nbatch = 1, curbatch = 0, nbatch_original = 1, nbatch_outstart = 1, growEnabled = true, totalTuples = 10000, partialTuples = 10000, skewTuples = 0, innerBatchFile = 0x0, outerBatchFile = 0x0, outer_hashfunctions = 0x2bdc228, inner_hashfunctions = 0x2bdc280, hashStrict = 0x2bdc2d8, spaceUsed = 677754, spaceAllowed = 16777216, spacePeak = 677754, spaceUsedSkew = 0, spaceAllowedSkew = 335544, hashCxt = 0x2bdc110, batchCxt = 0x2bde120, chunks = 0x2c708f0, current_chunk = 0x0, area = 0x0, parallel_state = 0x0, batches = 0x0, current_chunk_shared = 0}
hash桶中的元组&hash值
(gdb) p *hashTupleCannot access memory at address 0x0(gdb) p hashvalue$4 = 2324234220(gdb)
从常规hash桶中获取hash元组
(gdb) n1924 else if (hjstate->hj_CurSkewBucketNo != INVALID_SKEW_BUCKET_NO)(gdb) p hjstate->hj_CurSkewBucketNo$5 = -1(gdb) n1927 hashTuple = hashtable->buckets.unshared[hjstate->hj_CurBucketNo];(gdb) 1929 while (hashTuple != NULL)(gdb) p hjstate->hj_CurBucketNo$7 = 16364(gdb) p *hashTuple$6 = {next = {unshared = 0x0, shared = 0}, hashvalue = 1822113772}
判断hash值是否一致
(gdb) n1931 if (hashTuple->hashvalue == hashvalue)(gdb) p hashTuple->hashvalue$8 = 1822113772(gdb) p hashvalue$9 = 2324234220(gdb)
不一致,继续下一个元组
(gdb) n1948 hashTuple = hashTuple->next.unshared;(gdb) 1929 while (hashTuple != NULL)
下一个元组为NULL,返回F,说明没有匹配的元组
(gdb) p *hashTupleCannot access memory at address 0x0(gdb) n1954 return false;
在ExecStoreMinimalTuple上设置断点(这时候Hash值是一致的)
(gdb) b ExecStoreMinimalTupleBreakpoint 2 at 0x6e8cbf: file execTuples.c, line 427.(gdb) cContinuing.Breakpoint 1, ExecScanHashBucket (hjstate=0x2bb8738, econtext=0x2bb8950) at nodeHash.c:19101910 ExprState *hjclauses = hjstate->hashclauses;(gdb) del 1(gdb) cContinuing.Breakpoint 2, ExecStoreMinimalTuple (mtup=0x2be81b0, slot=0x2bb9c18, shouldFree=false) at execTuples.c:427427 Assert(mtup != NULL);(gdb) finishRun till exit from #0 ExecStoreMinimalTuple (mtup=0x2be81b0, slot=0x2bb9c18, shouldFree=false) at execTuples.c:4270x00000000006ff335 in ExecScanHashBucket (hjstate=0x2bb8738, econtext=0x2bb8950) at nodeHash.c:19361936 inntuple = ExecStoreMinimalTuple(HJTUPLE_MINTUPLE(hashTuple),Value returned is $10 = (TupleTableSlot *) 0x2bb9c18(gdb) n1939 econtext->ecxt_innertuple = inntuple;
匹配成功,返回T
(gdb) n1941 if (ExecQualAndReset(hjclauses, econtext))(gdb) 1943 hjstate->hj_CurTuple = hashTuple;(gdb) 1944 return true;(gdb) 1955 }(gdb)
HJ_SCAN_BUCKET阶段,实现的逻辑是扫描Hash桶,寻找inner relation中与outer relation元组匹配的元组,如匹配,则把匹配的Tuple存储在hjstate->hj_CurTuple中.
"PostgreSQL的ExecHashJoin依赖其他函数的实现逻辑是什么"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!
文件
存储
批次
上下
上下文
函数
条件
内存
数据
数组
空间
类型
哈希
结构
分配
一致
状态
第一次
查询
逻辑
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
2021中小学生网络安全知识
蛋壳app.服务器错误
oss文件下载服务器
虹口区服务器回收推荐厂家
服务器管理的用户和组不见了
数据库事务描述错误的是
山西麻将软件开发哪家专业
冠鼎网络技术有限公司怎么样
ip广播软件开发公司
水星虚拟服务器设置
服务器延时2毫秒
免流服务器的搭建
服务器一般有多少个硬盘
一起年轻互联网科技
QT数据无法连接数据库
国家网络安全宣传周活动报导
金融行业使用什么数据库
扬州个人软件开发推荐咨询
网络技术女生非技术类职业
毒app软件开发者
西软系统数据库无法登录
通用软件开发方向专业
天刀一代宗师服务器合服时间
学校服务器管理账号
金融可以学什么软件开发
国家级网络安全战
河南手机软件开发应用
日常生活中的网络安全
数据库扩展空间
厦门大学网络安全好考吗