PostgreSQL 原始碼解讀(93)- 查詢語句#77(ExecHashJoin函式#3)
本節是ExecHashJoin函式介紹的第三部分,主要介紹了ExecHashJoin中依賴的其他函式的實現邏輯,這些函式在HJ_NEED_NEW_OUTER階段中使用,包括ExecHashJoinOuterGetTuple、ExecPrepHashTableForUnmatched、ExecHashGetBucketAndBatch、ExecHashGetSkewBucket、ExecHashJoinSaveTuple和ExecFetchSlotMinimalTuple等。
一、資料結構
Plan
所有計劃節點透過將Plan結構作為第一個欄位從Plan結構“派生”。這確保了在將節點轉換為計劃節點時,一切都能正常工作。(在執行器中以通用方式傳遞時,節點指標經常被轉換為Plan *)
/* ----------------
* Plan node
*
* All plan nodes "derive" from the Plan structure by having the
* Plan structure as the first field. This ensures that everything works
* when nodes are cast to Plan's. (node pointers are frequently cast to Plan*
* when passed around generically in the executor)
* 所有計劃節點透過將Plan結構作為第一個欄位從Plan結構“派生”。
* 這確保了在將節點轉換為計劃節點時,一切都能正常工作。
* (在執行器中以通用方式傳遞時,節點指標經常被轉換為Plan *)
*
* We never actually instantiate any Plan nodes; this is just the common
* abstract superclass for all Plan-type nodes.
* 從未例項化任何Plan節點;這只是所有Plan-type節點的通用抽象超類。
* ----------------
*/
typedef struct Plan
{
NodeTag type;//節點型別
/*
* 成本估算資訊;estimated execution costs for plan (see costsize.c for more info)
*/
Cost startup_cost; /* 啟動成本;cost expended before fetching any tuples */
Cost total_cost; /* 總成本;total cost (assuming all tuples fetched) */
/*
* 最佳化器估算資訊;planner's estimate of result size of this plan step
*/
double plan_rows; /* 行數;number of rows plan is expected to emit */
int plan_width; /* 平均行大小(Byte為單位);average row width in bytes */
/*
* 並行執行相關的資訊;information needed for parallel query
*/
bool parallel_aware; /* 是否參與並行執行邏輯?engage parallel-aware logic? */
bool parallel_safe; /* 是否並行安全;OK to use as part of parallel plan? */
/*
* Plan型別節點通用的資訊.Common structural data for all Plan types.
*/
int plan_node_id; /* unique across entire final plan tree */
List *targetlist; /* target list to be computed at this node */
List *qual; /* implicitly-ANDed qual conditions */
struct Plan *lefttree; /* input plan tree(s) */
struct Plan *righttree;
List *initPlan; /* Init Plan nodes (un-correlated expr
* subselects) */
/*
* Information for management of parameter-change-driven rescanning
* parameter-change-driven重掃描的管理資訊.
*
* extParam includes the paramIDs of all external PARAM_EXEC params
* affecting this plan node or its children. setParam params from the
* node's initPlans are not included, but their extParams are.
*
* allParam includes all the extParam paramIDs, plus the IDs of local
* params that affect the node (i.e., the setParams of its initplans).
* These are _all_ the PARAM_EXEC params that affect this node.
*/
Bitmapset *extParam;
Bitmapset *allParam;
} Plan;
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))
二、原始碼解讀
ExecHashJoinOuterGetTuple
獲取非並行模式下hashjoin的下一個外部元組:要麼在第一次執行外部plan節點,要麼從hashjoin批處理的臨時檔案中獲取。
/*----------------------------------------------------------------------------------------------------
HJ_NEED_NEW_OUTER 階段
----------------------------------------------------------------------------------------------------*/
/*
* ExecHashJoinOuterGetTuple
*
* get the next outer tuple for a parallel oblivious hashjoin: either by
* executing the outer plan node in the first pass, or from the temp
* files for the hashjoin batches.
* 獲取非並行模式下hashjoin的下一個外部元組:要麼在第一次執行外部plan節點,要麼從hashjoin批處理的臨時檔案中獲取。
*
* Returns a null slot if no more outer tuples (within the current batch).
* 如果沒有更多外部元組(在當前批處理中),則返回空slot。
*
* On success, the tuple's hash value is stored at *hashvalue --- this is
* either originally computed, or re-read from the temp file.
* 如果成功,tuple的雜湊值儲存在輸入引數*hashvalue中——這是最初計算的,或者是從臨時檔案中重新讀取的。
*/
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) /* 第一個批次;if it is the first pass */
{
/*
* Check to see if first outer tuple was already fetched by
* ExecHashJoin() and not used yet.
* 檢查第一個外部元組是否已經由ExecHashJoin()函式獲取且尚未使用。
*/
slot = hjstate->hj_FirstOuterTupleSlot;
if (!TupIsNull(slot))
hjstate->hj_FirstOuterTupleSlot = NULL;//重置slot
else
slot = ExecProcNode(outerNode);//如為NULL,則獲取slot
while (!TupIsNull(slot))//slot不為NULL
{
/*
* We have to compute the tuple's hash value.
* 計算hash值
*/
ExprContext *econtext = hjstate->js.ps.ps_ExprContext;//表示式計算上下文
econtext->ecxt_outertuple = slot;//儲存獲取的slot
if (ExecHashGetHashValue(hashtable, econtext,
hjstate->hj_OuterHashKeys,
true, /* outer tuple */
HJ_FILL_OUTER(hjstate),
hashvalue))//計算Hash值
{
/* remember outer relation is not empty for possible rescan */
hjstate->hj_OuterNotEmpty = true;//設定標記(outer不為空)
return slot;//返回匹配的slot
}
/*
* That tuple couldn't match because of a NULL, so discard it and
* continue with the next one.
* 該元組無法匹配,丟棄它,繼續下一個元組。
*/
slot = ExecProcNode(outerNode);//繼續獲取下一個
}
}
else if (curbatch < hashtable->nbatch)//不是第一個批次
{
BufFile *file = hashtable->outerBatchFile[curbatch];//獲取緩衝的檔案
/*
* In outer-join cases, we could get here even though the batch file
* is empty.
* 在外連線的情況下,即使批處理檔案是空的,也可以在這裡進行處理。
*/
if (file == NULL)
return NULL;//如檔案為NULL,則返回
slot = ExecHashJoinGetSavedTuple(hjstate,
file,
hashvalue,
hjstate->hj_OuterTupleSlot);//從檔案中獲取slot
if (!TupIsNull(slot))
return slot;//非NULL,則返回
}
/* End of this batch */
//已完成,則返回NULL
return NULL;
}
/*
* ExecHashGetHashValue
* Compute the hash value for a tuple
* ExecHashGetHashValue - 計算元組的Hash值
*
* The tuple to be tested must be in either econtext->ecxt_outertuple or
* econtext->ecxt_innertuple. Vars in the hashkeys expressions should have
* varno either OUTER_VAR or INNER_VAR.
* 要測試的元組必須位於econtext->ecxt_outertuple或econtext->ecxt_innertuple中。
* hashkeys表示式中的Vars應該具有varno,即OUTER_VAR或INNER_VAR。
*
* A true result means the tuple's hash value has been successfully computed
* and stored at *hashvalue. A false result means the tuple cannot match
* because it contains a null attribute, and hence it should be discarded
* immediately. (If keep_nulls is true then false is never returned.)
* T意味著tuple的雜湊值已經成功計算並儲存在*hashvalue引數中。
* F意味著元組不能匹配,因為它包含null屬性,因此應該立即丟棄它。
* (如果keep_nulls為真,則永遠不會返回F。)
*/
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;
/*
* We reset the eval context each time to reclaim any memory leaked in the
* hashkey expressions.
* 我們每次重置eval上下文來回收hashkey表示式中分配的記憶體。
*/
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;
/* rotate hashkey left 1 bit at each step */
//雜湊鍵左移1位
hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
/*
* Get the join attribute value of the tuple
* 獲取元組的連線屬性值
*/
keyval = ExecEvalExpr(keyexpr, econtext, &isNull);
/*
* If the attribute is NULL, and the join operator is strict, then
* this tuple cannot pass the join qual so we can reject it
* immediately (unless we're scanning the outside of an outer join, in
* which case we must not reject it). Otherwise we act like the
* hashcode of NULL is zero (this will support operators that act like
* IS NOT DISTINCT, though not any more-random behavior). We treat
* the hash support function as strict even if the operator is not.
* 如果屬性為NULL,並且join運算子是嚴格的,那麼這個元組不能傳遞連線條件join qual,
* 因此可以立即拒絕它(除非正在掃描外連線的外表,在這種情況下不能拒絕它)。
* 否則,我們的行為就好像NULL的雜湊碼是零一樣(這將支援IS NOT DISTINCT運算子,但不會有任何隨機的情況出現)。
* 即使運算子不是嚴格的,也將雜湊函式視為嚴格的。
*
* Note: currently, all hashjoinable operators must be strict since
* the hash index AM assumes that. However, it takes so little extra
* code here to allow non-strict that we may as well do it.
* 注意:目前,所有雜湊可連線運算子都必須嚴格,因為雜湊索引AM假定如此。
* 但是,這裡只需要很少的額外程式碼就可以實現非嚴格性,我們也可以這樣做。
*/
if (isNull)
{
//NULL值
if (hashtable->hashStrict[i] && !keep_nulls)
{
MemoryContextSwitchTo(oldContext);
//不保持NULL值,不匹配
return false; /* cannot match */
}
/* else, leave hashkey unmodified, equivalent to hashcode 0 */
//否則的話,不修改hashkey,仍為0
}
else
{
//不為NULL
/* Compute the hash function */
//計算hash值
uint32 hkey;
hkey = DatumGetUInt32(FunctionCall1(&hashfunctions[i], keyval));
hashkey ^= hkey;
}
i++;//下一個鍵
}
//切換上下文
MemoryContextSwitchTo(oldContext);
//返回Hash鍵值
*hashvalue = hashkey;
return true;//成功獲取
}
ExecPrepHashTableForUnmatched
為ExecScanHashTableForUnmatched函式呼叫作準備
/*
* ExecPrepHashTableForUnmatched
* set up for a series of ExecScanHashTableForUnmatched calls
* 為ExecScanHashTableForUnmatched函式呼叫作準備
*/
void
ExecPrepHashTableForUnmatched(HashJoinState *hjstate)
{
/*----------
* During this scan we use the HashJoinState fields as follows:
*
* hj_CurBucketNo: next regular bucket to scan
* hj_CurSkewBucketNo: next skew bucket (an index into skewBucketNums)
* hj_CurTuple: last tuple returned, or NULL to start next bucket
* 在這次掃描期間,我們使用HashJoinState結構體中的欄位如下:
* hj_CurBucketNo: 下一個常規的bucket
* hj_CurSkewBucketNo: 下一個個傾斜的bucket
* hj_CurTuple: 最後返回的元組,或者為NULL(下一個bucket開始)
*----------
*/
hjstate->hj_CurBucketNo = 0;
hjstate->hj_CurSkewBucketNo = 0;
hjstate->hj_CurTuple = NULL;
}
ExecHashGetBucketAndBatch
確定雜湊值的bucket號和批處理號
/*
* ExecHashGetBucketAndBatch
* Determine the bucket number and batch number for a hash value
* ExecHashGetBucketAndBatch
* 確定雜湊值的bucket號和批處理號
*
* Note: on-the-fly increases of nbatch must not change the bucket number
* for a given hash code (since we don't move tuples to different hash
* chains), and must only cause the batch number to remain the same or
* increase. Our algorithm is
* bucketno = hashvalue MOD nbuckets
* batchno = (hashvalue DIV nbuckets) MOD nbatch
* where nbuckets and nbatch are both expected to be powers of 2, so we can
* do the computations by shifting and masking. (This assumes that all hash
* functions are good about randomizing all their output bits, else we are
* likely to have very skewed bucket or batch occupancy.)
* 注意:nbatch的動態增加不能更改給定雜湊碼的桶號(因為我們不將元組移動到不同的雜湊鏈),
* 並且只能使批號保持不變或增加。我們的演算法是:
* bucketno = hashvalue MOD nbuckets
* batchno = (hashvalue DIV nbuckets) MOD nbatch
* 這裡nbucket和nbatch都是2的冪,所以我們可以透過移動和遮蔽來進行計算。
* (這假定所有雜湊函式都能很好地隨機化它們的所有輸出位,否則很可能會出現非常傾斜的桶或批處理佔用。)
*
* nbuckets and log2_nbuckets may change while nbatch == 1 because of dynamic
* bucket count growth. Once we start batching, the value is fixed and does
* not change over the course of the join (making it possible to compute batch
* number the way we do here).
* 當nbatch == 1時,由於動態bucket計數的增長,nbucket和log2_nbucket可能會發生變化。
* 一旦開始批處理,這個值就固定了,並且在連線過程中不會改變(這使得我們可以像這裡那樣計算批號)。
*
* nbatch is always a power of 2; we increase it only by doubling it. This
* effectively adds one more bit to the top of the batchno.
* nbatch總是2的冪;我們只是透過x2來調整。這相當於為批號的頭部增加了一位。
*/
void
ExecHashGetBucketAndBatch(HashJoinTable hashtable,
uint32 hashvalue,
int *bucketno,
int *batchno)
{
uint32 nbuckets = (uint32) hashtable->nbuckets;//桶數
uint32 nbatch = (uint32) hashtable->nbatch;//批次號
if (nbatch > 1)//批次>1
{
/* we can do MOD by masking, DIV by shifting */
//我們可以透過遮蔽來實現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。
/*
* ExecHashGetSkewBucket
*
* Returns the index of the skew bucket for this hashvalue,
* or INVALID_SKEW_BUCKET_NO if the hashvalue is not
* associated with any active skew bucket.
* 返回這個雜湊值的傾斜桶的索引,如果雜湊值與任何活動的傾斜桶沒有關聯,則返回INVALID_SKEW_BUCKET_NO。
*/
int
ExecHashGetSkewBucket(HashJoinTable hashtable, uint32 hashvalue)
{
int bucket;
/*
* Always return INVALID_SKEW_BUCKET_NO if not doing skew optimization (in
* particular, this happens after the initial batch is done).
* 如果不進行傾斜最佳化(特別是在初始批處理完成之後),則返回INVALID_SKEW_BUCKET_NO。
*/
if (!hashtable->skewEnabled)
return INVALID_SKEW_BUCKET_NO;
/*
* Since skewBucketLen is a power of 2, we can do a modulo by ANDing.'
* 由於skewBucketLen是2的冪,可以透過AND操作來做一個模。
*/
bucket = hashvalue & (hashtable->skewBucketLen - 1);
/*
* While we have not hit a hole in the hashtable and have not hit the
* desired bucket, we have collided with some other hash value, so try the
* next bucket location.
* 雖然我們沒有在雜湊表中找到一個hole,也沒有找到所需的bucket,
* 但是與其他一些雜湊值發生了衝突,所以嘗試下一個bucket位置。
*/
while (hashtable->skewBucket[bucket] != NULL &&
hashtable->skewBucket[bucket]->hashvalue != hashvalue)
bucket = (bucket + 1) & (hashtable->skewBucketLen - 1);
/*
* Found the desired bucket?
* 找到了bucket,返回
*/
if (hashtable->skewBucket[bucket] != NULL)
return bucket;
/*
* There must not be any hashtable entry for this hash value.
*/
//否則返回INVALID_SKEW_BUCKET_NO
return INVALID_SKEW_BUCKET_NO;
}
ExecHashJoinSaveTuple
在批處理檔案中儲存元組.每個元組在檔案中記錄的是它的雜湊值,然後是最小化格式的元組。
/*
* ExecHashJoinSaveTuple
* save a tuple to a batch file.
* 在批處理檔案中儲存元組
*
* The data recorded in the file for each tuple is its hash value,
* then the tuple in MinimalTuple format.
* 每個元組在檔案中記錄的是它的雜湊值,然後是最小化格式的元組。
*
* Note: it is important always to call this in the regular executor
* context, not in a shorter-lived context; else the temp file buffers
* will get messed up.
* 注意:在常規執行程式上下文中呼叫它總是很重要的,而不是在較短的生命週期中呼叫它;
* 否則臨時檔案緩衝區就會出現混亂。
*/
void
ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue,
BufFile **fileptr)
{
BufFile *file = *fileptr;//檔案指標
size_t written;//寫入大小
if (file == NULL)
{
/* First write to this batch file, so open it. */
//檔案指標為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的資料
/* --------------------------------
* ExecFetchSlotMinimalTuple
* Fetch the slot's minimal physical tuple.
* 以最小化物理元組的格式提取slot的資料.
*
* If the given tuple table slot can hold a minimal tuple, indicated by a
* non-NULL get_minimal_tuple callback, the function returns the minimal
* tuple returned by that callback. It assumes that the minimal tuple
* returned by the callback is "owned" by the slot i.e. the slot is
* responsible for freeing the memory consumed by the tuple. Hence it sets
* *shouldFree to false, indicating that the caller should not free the
* memory consumed by the minimal tuple. In this case the returned minimal
* tuple should be considered as read-only.
* 如果給定的元組table slot可以儲存由non-NULL get_minimal_tuple回撥函式指示的最小元組,
* 則函式將返回該回撥函式返回的最小元組。
* 它假定回撥函式返回的最小元組由slot“擁有”,即slot負責釋放元組所消耗的記憶體。
* 因此,它將*shouldFree設定為false,表示呼叫方不應該釋放記憶體。
* 在這種情況下,返回的最小元組應該被認為是隻讀的。
*
* If that callback is not supported, it calls copy_minimal_tuple callback
* which is expected to return a copy of minimal tuple represnting the
* contents of the slot. In this case *shouldFree is set to true,
* indicating the caller that it should free the memory consumed by the
* minimal tuple. In this case the returned minimal tuple may be written
* up.
* 如果不支援該回撥函式,則呼叫copy_minimal_tuple回撥函式,
* 該回撥將返回一個表示slot內容的最小元組副本。
* *shouldFree被設定為true,這表示呼叫者應該釋放記憶體。
* 在這種情況下,可以寫入返回的最小元組。
* --------------------------------
*/
MinimalTuple
ExecFetchSlotMinimalTuple(TupleTableSlot *slot,
bool *shouldFree)
{
/*
* sanity checks
* 安全檢查
*/
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) /* if it is the first pass */
(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)
DONE!
四、參考資料
Hash Joins: Past, Present and Future/PGCon 2017
A Look at How Postgres Executes a Tiny Join - Part 1
A Look at How Postgres Executes a Tiny Join - Part 2
Assignment 2 Symmetric Hash Join
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6906/viewspace-2374797/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- PostgreSQL 原始碼解讀(90)- 查詢語句#75(ExecHashJoin函式#1)SQL原始碼函式
- PostgreSQL 原始碼解讀(91)- 查詢語句#76(ExecHashJoin函式#2)SQL原始碼函式
- PostgreSQL 原始碼解讀(95)- 查詢語句#78(ExecHashJoin函式#4-H...SQL原始碼函式
- PostgreSQL 原始碼解讀(97)- 查詢語句#79(ExecHashJoin函式#5-H...SQL原始碼函式
- PostgreSQL 原始碼解讀(77)- 查詢語句#62(create_plan函式#1-主實...SQL原始碼函式
- PostgreSQL 原始碼解讀(83)- 查詢語句#68(PortalStart函式)SQL原始碼函式
- PostgreSQL 原始碼解讀(40)- 查詢語句#25(query_planner函式#3)SQL原始碼函式
- PostgreSQL 原始碼解讀(82)- 查詢語句#67(PortalXXX系列函式)SQL原始碼函式
- PostgreSQL 原始碼解讀(89)- 查詢語句#74(SeqNext函式#2)SQL原始碼函式
- PostgreSQL 原始碼解讀(88)- 查詢語句#73(SeqNext函式#1)SQL原始碼函式
- PostgreSQL 原始碼解讀(73)- 查詢語句#58(grouping_planner函式...SQL原始碼函式
- PostgreSQL 原始碼解讀(79)- 查詢語句#64(create_plan函式#3-Se...SQL原始碼函式
- PostgreSQL 原始碼解讀(18)- 查詢語句#3(SQL Parse)SQL原始碼
- PostgreSQL 原始碼解讀(43)- 查詢語句#28(query_planner函式#5)SQL原始碼函式
- PostgreSQL 原始碼解讀(45)- 查詢語句#30(query_planner函式#6)SQL原始碼函式
- PostgreSQL 原始碼解讀(46)- 查詢語句#31(query_planner函式#7)SQL原始碼函式
- PostgreSQL 原始碼解讀(47)- 查詢語句#32(query_planner函式#8)SQL原始碼函式
- PostgreSQL 原始碼解讀(48)- 查詢語句#33(query_planner函式#9)SQL原始碼函式
- PostgreSQL 原始碼解讀(38)- 查詢語句#23(query_planner函式#1)SQL原始碼函式
- PostgreSQL 原始碼解讀(39)- 查詢語句#24(query_planner函式#2)SQL原始碼函式
- PostgreSQL 原始碼解讀(41)- 查詢語句#26(query_planner函式#4)SQL原始碼函式
- PostgreSQL 原始碼解讀(53)- 查詢語句#38(make_one_rel函式#3-順...SQL原始碼函式
- PostgreSQL 原始碼解讀(33)- 查詢語句#18(查詢優化-表示式預處理#3)SQL原始碼優化
- PostgreSQL 原始碼解讀(24)- 查詢語句#9(查詢重寫)SQL原始碼
- PostgreSQL 原始碼解讀(70)- 查詢語句#55(make_one_rel函式#20-...SQL原始碼函式
- PostgreSQL 原始碼解讀(71)- 查詢語句#56(make_one_rel函式#21-...SQL原始碼函式
- PostgreSQL 原始碼解讀(67)- 查詢語句#52(make_one_rel函式#17-...SQL原始碼函式
- PostgreSQL 原始碼解讀(68)- 查詢語句#53(make_one_rel函式#18-...SQL原始碼函式
- PostgreSQL 原始碼解讀(69)- 查詢語句#54(make_one_rel函式#19-...SQL原始碼函式
- PostgreSQL 原始碼解讀(66)- 查詢語句#51(make_one_rel函式#16-...SQL原始碼函式
- PostgreSQL 原始碼解讀(80)- 查詢語句#65(create_plan函式#4-Jo...SQL原始碼函式
- PostgreSQL 原始碼解讀(78)- 查詢語句#63(create_plan函式#2-cr...SQL原始碼函式
- PostgreSQL 原始碼解讀(72)- 查詢語句#57(make_one_rel函式#22-...SQL原始碼函式
- PostgreSQL 原始碼解讀(65)- 查詢語句#50(make_one_rel函式#15-...SQL原始碼函式
- PostgreSQL 原始碼解讀(62)- 查詢語句#47(make_one_rel函式#12-...SQL原始碼函式
- PostgreSQL 原始碼解讀(63)- 查詢語句#48(make_one_rel函式#13-...SQL原始碼函式
- PostgreSQL 原始碼解讀(64)- 查詢語句#49(make_one_rel函式#14-...SQL原始碼函式
- PostgreSQL 原始碼解讀(60)- 查詢語句#45(make_one_rel函式#10-...SQL原始碼函式