Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the implementation logic of PostgreSQL's ExecHashJoin dependence on other functions?

2025-04-12 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Database >

Share

Shulou(Shulou.com)05/31 Report--

This article introduces the relevant knowledge of "what is the implementation logic of PostgreSQL's ExecHashJoin relying on other functions". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

I. data structure

JoinState

The base class of Hash/NestLoop/Merge Join

/ *-* JoinState information * * Superclass for state nodes of join plans. * Hash/NestLoop/Merge Join base class *-* / typedef struct JoinState {PlanState ps;// base class PlanState JoinType jointype;// connection type / / when a matching inner tuple is found, if you need to jump to the next outer tuple, the value is T bool single_match. / * True if we should skip to next outer tuple * after finding one inner match * / / join condition expression (except ps.qual) ExprState * joinqual; / * JOIN quals (in addition to ps.qual) * /} JoinState

HashJoinState

Hash Join runtime status structure

/ * these structs are defined in executor/hashjoin.h: * / typedef struct HashJoinTupleData * HashJoinTuple;typedef struct HashJoinTableData * HashJoinTable;typedef struct HashJoinState {JoinState js; / * Base class; its first field is NodeTag * / ExprState * hashclauses;//hash connection condition List * hj_OuterHashKeys; / * external condition linked list; list of ExprState nodes * / List * hj_InnerHashKeys; / * Internal table connection condition List of ExprState nodes * / List * hj_HashOperators; / * operator OIDs linked list; list of operator OIDs * / HashJoinTable hj_HashTable;//Hash table uint32 hj_CurHashValue;// current hash value int hj_CurBucketNo;// current bucket number int hj_CurSkewBucketNo;// line slant bucket number HashJoinTuple hj_CurTuple;// current tuple TupleTableSlot * hj_OuterTupleSlot / / outer relation slot TupleTableSlot * hj_HashTupleSlot;//Hash tuple slot TupleTableSlot * hj_NullOuterTupleSlot;// outer virtual slot TupleTableSlot * hj_NullInnerTupleSlot;// for external connection inner virtual slot TupleTableSlot * hj_FirstOuterTupleSlot;// int hj_JoinState;//JoinState status bool hj_MatchedOuter;// matches whether bool hj_OuterNotEmpty;//outer relation is empty} HashJoinState

HashJoinTable

Hash data structure

Typedef struct HashJoinTableData {hash barrels in int nbuckets; / * memory; logarithm of # buckets in the in-memory hash table * / int log2_nbuckets; / * 2 (nbuckets must be a power of 2); its log2 (nbuckets must be a power of 2) * / int nbuckets_original; / * the number of barrels in the first hash; # buckets when starting the first hash * / int nbuckets_optimal / * optimized number of barrels (per batch); logarithm of 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] is the head item union {/ * unshared array is per-batch storage of the tuple linked list in the first bucket in memory, as are all the tuples * / / unshared arrays are stored in batches, all tuples are like this struct HashJoinTupleData * * unshared / * shared array is per-query DSA area, as are all the tuples * / / shared array is the DSA area of each query. All tuples are such dsa_pointer_atomic * shared;} buckets; bool keepNulls; / * if they do not match, NULL tuples are stored. The value is true to store unmatchable NULL tuples * / bool skewEnabled; / *. Do you use skew optimization? Are we using skew optimization? * / HashSkewBucket * * skewBucket; / * number of tilted hash buckets; hashtable of skew buckets * / int skewBucketLen; / * skewBucket array size; size of skewBucket array (a power of 2!) * / int nSkewBuckets; / * active tilted buckets; number of active skew buckets * / int * skewBucketNums; / * active tilt bucket array index Array indexes of active skew buckets * / int nbatch; / * number of batches; number of batches * / int curbatch; / * current batch, the first round is 0 * current batch #; 0 during 1st pass * / int nbatch_original; / * batch at the start of inner scanning; nbatch when we started inner scan * / int nbatch_outstart / * batch at start of outer scan; nbatch when we started outer scan * / bool growEnabled; / * turn off the tag added by nbatch; flag to shut off nbatch increases * / double totalTuples; / * number of tuples obtained from inner plan; # tuples obtained from inner plan * / double partialTuples; / * number of inner tuples obtained through hashjoin; # tuples obtained from inner plan by me * / double skewTuples / * number of tilted tuples; # 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. * these arrays are allocated during the lifetime of the hash join, but only if nbatch > 1. * the file is opened only when the tuple is written to the file for the first time (otherwise its pointer will remain NULL). * Note that the 0 th array element is never used because the tuple of batch 0 is never dumped. * / BufFile * * innerBatchFile; / * inner virtual temporary file cache for each batch; buffered virtual temp file per batch * / BufFile * * outerBatchFile; / * outer virtual temporary file cache for each batch; 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). * Information about the data type-specific hash function of the data type being hashed. * these arrays are the same length as the number of hash join clauses (hash keys). * / FmgrInfo * outer_hashfunctions; / * outer hash function FmgrInfo structure; lookup data for hashfunctions * / FmgrInfo * inner_hashfunctions; / * inner hash function FmgrInfo structure; lookup data for hashfunctions * / bool * hashStrict; / * each hash operator is strict? current memory space used by is each hash join operator strict? * / Size spaceUsed; / * tuple Memory space currently used by tuples * / Size spaceAllowed; / * space usage limit; upper limit for space used * / Size spacePeak; / * peak space usage; current space usage of peak space used * / Size spaceUsedSkew; / * tilted hash table; upper limit of skew hash table's current space usage * / Size spaceAllowedSkew; / * tilted hash table usage Upper limit for skew hashtable * / MemoryContext hashCxt; / * the context of the entire hash connection storage; context for whole-hash-join storage * / MemoryContext batchCxt; / * the context of the batch storage; context for this-batch-only storage * / * used for dense allocation of tuples (into linked chunks) * / / for dense allocation of tuples (to link blocks) HashMemoryChunk chunks; / * the whole batch uses a linked list One list for the whole batch * / * Shared and private state for Parallel Hash. * / / shared and private status used by parallel hash HashMemoryChunk current_chunk; / * current chunk;this backend's current chunk of background processes * / dsa_area * area; / * DSA area used to allocate memory; DSA area to allocate memory from * / ParallelHashJoinState * parallel_state;// parallel execution status ParallelHashJoinBatchAccessor * batches;// parallel accessor dsa_pointer current_chunk_shared / / start pointer of the current chunk} HashJoinTableData;typedef struct HashJoinTableData * HashJoinTable

HashJoinTupleData

Hash connection tuple data

/ *-* 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.) * each active hashjoin has a hashable control block, which is allocated through palloc in each query context of the executing program. * all other storage required by hashjoin is kept in a private memory context, with two for each hashjoin. This makes it easy and fast to release it when it is no longer needed. * (exception: data related to temporary files also exists in the context of each query, because buffile.c is always called in this case.) * * 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. The (Likewise, any temporary files we make will * be cleaned up by the virtual file manager in event of an error.) * hashtable context is a subcontext of each query context, ensuring that they are discarded at the end of the statement, even if the connection is aborted early due to an error. * (similarly, if an error occurs, the virtual file manager cleans up any temporary files created.) * * 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. * Storage space over the entire connection should be allocated from "hashCxt", while only storage space that requires the current batch should be allocated in "batchCxt". * by resetting the batchCxt at the end of each batch, you can reliably release all storage for each batch without feeling tedious. * 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. * in the first scan of the internal relationship, its tuple was obtained from the executor. * if nbatch > 1, tuples that are not part of the first batch will be saved to an intra-batch temporary file. * the same statement applies to the first scan of the external relationship, but we write the tuple to the external batch temporary file. * after the first scan, we do the following for the remaining tuples of each batch: * 1. The tuple is read from the internal batch file and loaded into the hash bucket. * 2. Reads tuples from an external batch file, matching hash buckets and output. * 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. * if the hash table in memory is too large, you can dynamically increase the nbatch. * the calculation of hash values to batches is arranged as follows: * this only causes tuples to enter later batches than previously thought, not earlier batches. * when nbatch is added, rescan the hash table and dump any tuples that now belong to later batches to the correct internal batch file. * later, when reading internal or external batch files, you may find tuples that are no longer part of the current batch; * if so, simply dump them to the correct batch file. *-* / / * 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 the hash value of the next tuple union {struct HashJoinTupleData * unshared; dsa_pointer shared;} next; uint32 hashvalue; / * tuple in the same bucket; 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)) II. Source code interpretation

ExecScanHashBucket

Search the hash bucket that matches the current outer relation tuple to find a matching inner relation tuple.

/ *- HJ_SCAN_BUCKET stage-* / * * ExecScanHashBucket * scan a Hash bucket for matches to the current outertuple * search for hash buckets that match the current outer relation tuple * * The current outertuple must be stored in econtext- > ecxt_outertuple. * the current outer relation tuple must be stored in econtext- > ecxt_outertuple * * On success, the innertuple is stored into hjstate- > hj_CurTuple and * econtext- > ecxt_innertuple, using hjstate- > hj_HashTupleSlot as the slot * for the latter. * after success, the internal tuples are stored in hjstate- > hj_CurTuple and econtext- > ecxt_innertuple. * use hjstate- > hj_HashTupleSlot as the slot of the latter. * / boolExecScanHashBucket (HashJoinState * hjstate, ExprContext * econtext) {ExprState * hjclauses = hjstate- > hashclauses;//hash join condition expression HashJoinTable hashtable = hjstate- > hj_HashTable;//Hash table HashJoinTuple hashTuple = hjstate- > hj_CurTuple;// current Tuple uint32 hashvalue = hjstate- > hj_CurHashValue;// hash value / * * 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 is the address of the tuple recently returned from the current bucket, or NULL if you need to start scanning the new bucket. * If the tuple hashed to a skew bucket then scan the skew bucket * otherwise scan the standard hashtable bucket. * if the tuple is hashed to the tilt bucket, scan the tilt bucket, otherwise scan the standard hash table bucket. * / if (hashTuple! = NULL) hashTuple = hashTuple- > next.unshared;//hashTuple, get the next else if through the pointer (hjstate- > hj_CurSkewBucketNo! = INVALID_SKEW_BUCKET_NO) / / if it is NULL and use tilt optimization, get hashTuple = hashtable- > skewBucket [hjstate-> hj_CurSkewBucketNo]-> tuples from the tilt bucket Else / if NULL, get hashTuple = hashtable- > buckets.unshared [hjstate-> hj_CurBucketNo] from regular bucket without using skew optimization; while (hashTuple! = NULL) / / loop {if (hashTuple- > hashvalue = = hashvalue) / / hash value consistent {TupleTableSlot * inntuple / / inner tuple / * insert hashtable's tuple into exec slot so ExecQual sees it * / / insert the tuple from the Hash table into the slot of the actuator, and use inntuple = ExecStoreMinimalTuple (HJTUPLE_MINTUPLE (hashTuple), hjstate- > hj_HashTupleSlot, false) as input to the function ExecQual. / * do not pfree * / econtext- > ecxt_innertuple = inntuple;// assignment if (ExecQualAndReset (hjclauses, econtext)) / / determine whether the connection condition satisfies {hjstate- > hj_CurTuple = hashTuple;//, then assign & return T return true;} hashTuple = hashTuple- > next.unshared / / get the next tuple} / * * no match * mismatch from the Hash table. Return F * / return false;} / * * Store a minimal tuple into TTSOpsMinimalTuple type slot. * Storage minimized tuple to TTSOpsMinimalTuple type slot * * If the target slot is not guaranteed to be TTSOpsMinimalTuple type slot, * use the, more expensive, ExecForceStoreMinimalTuple (). * if the target slot is not sure to be of type TTSOpsMinimalTuple, use the more expensive ExecForceStoreMinimalTuple () function * / TupleTableSlot * ExecStoreMinimalTuple (MinimalTuple mtup, TupleTableSlot * slot, bool shouldFree) {/ * sanity checks * security check * / Assert (mtup! = NULL); Assert (slot! = NULL); Assert (slot- > tts_tupleDescriptor! = NULL) If (unlikely (! TTS_IS_MINIMALTUPLE (slot) / / Type checking elog (ERROR, "trying to store a minimal tuple into wrong type of slot"); tts_minimal_store_tuple (slot, mtup, shouldFree); / / Storage return slot;// returns slot} static voidtts_minimal_store_tuple (TupleTableSlot * slot, MinimalTuple mtup, bool shouldFree) {MinimalTupleTableSlot * mslot = (MinimalTupleTableSlot *) slot;// to get slot tts_minimal_clear (slot) / / clear the original slot / / Security check Assert (! TTS_SHOULDFREE (slot)); Assert (TTS_EMPTY (slot)); / / set slot information slot- > tts_flags & = ~ TTS_FLAG_EMPTY; slot- > tts_nvalid = 0; mslot- > off = 0; / / store mslot- > mintuple = mtup; Assert (mslot- > tuple = = & mslot- > minhdr) in mslot 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 * / / there is no need to set t_sefl or t_tableOid because access to if (shouldFree) slot- > tts_flags is not allowed | = TTS_FLAG_SHOULDFREE Else Assert (! TTS_SHOULDFREE (slot));} / * * ExecQualAndReset ()-evaluate qual with ExecQual () and reset expression * context. * ExecQualAndReset ()-use ExecQual () to parse and reset the expression * / # ifndef FRONTENDstatic inline boolExecQualAndReset (ExprState * state, ExprContext * econtext) {bool ret = ExecQual (state, econtext); / / call ExecQual / * inline ResetExprContext, to avoid ordering issue in this file * / / inline ResetExprContext to avoid ordering MemoryContextReset (econtext- > ecxt_per_tuple_memory) in this file; return ret } # endif#define HeapTupleHeaderSetMatch (tup)\ (\ (tup)-> t_infomask2 | = HEAP_TUPLE_HAS_MATCH\) 3. Tracking analysis

The test script is as follows

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 jftestdb (# 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)

Start gdb and set breakpoint

(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

Set related variables

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 connection condition

(gdb) p * hjclauses$1 = {tag = {type = T_ExprState}, flags = 7'\ asides, 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 table

(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 = 10 000, skewTuples = 0, innerBatchFile = 0x0, 0x0 = 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}

Tuple in hash bucket & hash value

(gdb) p * hashTupleCannot access memory at address 0x0 (gdb) p hashvalue$4 = 2324234220 (gdb)

Get hash tuples from regular hash buckets

(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}

Determine whether the hash value is consistent

(gdb) n1931 if (hashTuple- > hashvalue = = hashvalue) (gdb) p hashTuple- > hashvalue$8 = 1822113772 (gdb) p hashvalue$9 = 2324234220 (gdb)

Inconsistent, proceed to the next tuple

(gdb) n1948 hashTuple = hashTuple- > next.unshared; (gdb) 1929 while (hashTuple! = NULL)

The next tuple is NULL, and F is returned, indicating that there are no matching tuples

(gdb) p * hashTupleCannot access memory at address 0x0 (gdb) n1954 return false

Set breakpoints on ExecStoreMinimalTuple (when hash values are consistent)

(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

If the match is successful, return T

(gdb) n1941 if (ExecQualAndReset (hjclauses, econtext)) (gdb) 1943 hjstate- > hj_CurTuple = hashTuple; (gdb) 1944 return true; (gdb) 1955} (gdb)

In the HJ_SCAN_BUCKET phase, the logic is to scan the Hash bucket to find the tuples in the inner relation that match the outer relation tuple. If they match, the matching Tuple is stored in hjstate- > hj_CurTuple.

This is the end of the content of "what is the implementation logic of other functions that PostgreSQL's ExecHashJoin depends on". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Database

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report